From 8cf9b3144d0d4e9197102fda4801132340de23f0 Mon Sep 17 00:00:00 2001 From: Nix User Date: Sun, 6 Sep 2026 15:51:14 +0530 Subject: [PATCH 01/17] Add goodixtls5e0a driver for Goodix 27c6:5e0a Image-type driver for the Goodix 27c6:5e0a TLS sensor (e.g. Realme Book): bulk USB transport, TLS 1.2 PSK session, hardware finger-detect gating, 64x80 native frames decoded to 128x160 inverted images for the in-tree NBIS/Bozorth3 matcher, plus suspend/resume and clean teardown. Derivation: clean-room reverse engineering from passive USB captures of Windows driver traffic. No vendor code included. PSK note: the driver carries a static 32-byte host PSK observed in the captures (flags 0xbb020001). The 0xe4-readable slot reports factory bytes (not the TLS key) and 0xe0 writes are rejected, so there is no on-device provisioning. Per-unit scope of this key is unconfirmed. Build: driver 'goodixtls5e0a' with 'goodixtls,openssl' helpers, registered in default drivers; udev hwdb regenerated (5E0A moves supported). --- README.md | 1 + data/autosuspend.hwdb | 6 +- libfprint/drivers/goodixtls/goodix.c | 1702 ++++++++++++++++++++ libfprint/drivers/goodixtls/goodix.h | 608 +++++++ libfprint/drivers/goodixtls/goodix5e0a.c | 956 +++++++++++ libfprint/drivers/goodixtls/goodix5e0a.h | 130 ++ libfprint/drivers/goodixtls/goodix5xx.c | 642 ++++++++ libfprint/drivers/goodixtls/goodix5xx.h | 248 +++ libfprint/drivers/goodixtls/goodix_proto.c | 141 ++ libfprint/drivers/goodixtls/goodix_proto.h | 175 ++ libfprint/drivers/goodixtls/goodixtls.c | 298 ++++ libfprint/drivers/goodixtls/goodixtls.h | 109 ++ libfprint/fprint-list-udev-hwdb.c | 1 - libfprint/meson.build | 6 + meson.build | 9 + 15 files changed, 5030 insertions(+), 2 deletions(-) create mode 100644 libfprint/drivers/goodixtls/goodix.c create mode 100644 libfprint/drivers/goodixtls/goodix.h create mode 100644 libfprint/drivers/goodixtls/goodix5e0a.c create mode 100644 libfprint/drivers/goodixtls/goodix5e0a.h create mode 100644 libfprint/drivers/goodixtls/goodix5xx.c create mode 100644 libfprint/drivers/goodixtls/goodix5xx.h create mode 100644 libfprint/drivers/goodixtls/goodix_proto.c create mode 100644 libfprint/drivers/goodixtls/goodix_proto.h create mode 100644 libfprint/drivers/goodixtls/goodixtls.c create mode 100644 libfprint/drivers/goodixtls/goodixtls.h diff --git a/README.md b/README.md index aec2e0b64..f4bd57e1d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ This is an experimental libfprint driver implementation for Goodix drivers. Currently in the works: - 27c6x5110 (80x64 resolution) +- 27c6:5e0a (Goodix TLS, 64x80 native / 128x160 scaled, e.g. Realme Book) *LibFPrint is part of the **[FPrint][Website]** project.* diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index d476f7e1a..6df2af6e3 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -190,6 +190,11 @@ usb:v27C6p6A94* ID_AUTOSUSPEND=1 ID_PERSIST=0 +# Supported by libfprint driver goodixtls5e0a +usb:v27C6p5E0A* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + # Supported by libfprint driver nb1010 usb:v298Dp1010* ID_AUTOSUSPEND=1 @@ -367,7 +372,6 @@ usb:v27C6p55A2* usb:v27C6p55A4* usb:v27C6p55B4* usb:v27C6p5740* -usb:v27C6p5E0A* usb:v27C6p581A* usb:v2808p9338* usb:v2808p93A9* diff --git a/libfprint/drivers/goodixtls/goodix.c b/libfprint/drivers/goodixtls/goodix.c new file mode 100644 index 000000000..d06b440b2 --- /dev/null +++ b/libfprint/drivers/goodixtls/goodix.c @@ -0,0 +1,1702 @@ +// Goodix Tls driver for libfprint + +// Copyright (C) 2021 Alexander Meiler +// Copyright (C) 2021 Matthieu CHARETTE +// Copyright (C) 2021 Natasha England-Elbro + +// 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 + +#include "fpi-log.h" +#include "fpi-ssm.h" +#include "fpi-usb-transfer.h" +#define FP_COMPONENT "goodixtls" + +#include +#include +#include +#include +#include +#include +#include + +#include "drivers_api.h" +#include "goodix.h" +#include "goodix_proto.h" +#include "goodixtls.h" + +typedef struct +{ + GoodixTlsServer *tls_hop; + + GSource *timeout; + + guint8 cmd; + + gboolean ack; + gboolean reply; + + GoodixCmdCallback callback; + gpointer user_data; + + guint8 *data; + guint32 length; + + GoodixCallbackInfo *tls_ready_callback; + + /* Stale-activation guard: bumped on (re)activation start and teardown; + * TLS completion callbacks drop on mismatch. */ + guint activation_gen; + + GCancellable *transfer_cancel_tkn; + gboolean inited; +} FpiDeviceGoodixTlsPrivate; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (FpiDeviceGoodixTls, fpi_device_goodixtls, + FP_TYPE_IMAGE_DEVICE); + +// TODO remove every GDestroyNotify +// TODO add cmd timeouts + +gchar * +data_to_str (guint8 *data, guint32 length) +{ + gchar *string = g_malloc ((length * 2) + 1); + + for (guint32 i = 0; i < length; i++) + g_snprintf (string + i * 2, 3, "%02x", data[i]); + + return string; +} + +// ---- GOODIX RECEIVE SECTION START ---- + +void +goodix_receive_done (FpDevice *dev, guint8 *data, guint16 length, + GError *error) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + GoodixCmdCallback callback = priv->callback; + gpointer user_data = priv->user_data; + + if (!(priv->ack || priv->reply)) + { + if (error) + g_error_free (error); + return; + } + + goodix_reset_state (dev); + if (!error) + fp_dbg ("Completed command: 0x%02x", priv->cmd); + + if (callback) + callback (dev, data, length, user_data, error); + else if (error) + g_error_free (error); +} + +void +goodix_receive_none (FpDevice *dev, guint8 *data, guint16 length, + gpointer user_data, GError *error) +{ + g_autofree GoodixCallbackInfo *cb_info = user_data; + GoodixNoneCallback callback = (GoodixNoneCallback) cb_info->callback; + + callback (dev, cb_info->user_data, error); +} + +void +goodix_receive_none_tolerant (FpDevice *dev, guint8 *data, guint16 length, + gpointer user_data, GError *error) +{ + g_autofree GoodixCallbackInfo *cb_info = user_data; + GoodixNoneCallback callback = (GoodixNoneCallback) cb_info->callback; + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) + g_clear_error (&error); /* ponytail: NOP silence = buffer already empty */ + + callback (dev, cb_info->user_data, error); +} + +void +goodix_receive_default (FpDevice *dev, guint8 *data, guint16 length, + gpointer user_data, GError *error) +{ + g_autofree GoodixCallbackInfo *cb_info = user_data; + GoodixDefaultCallback callback = (GoodixDefaultCallback) cb_info->callback; + + callback (dev, data, length, cb_info->user_data, error); +} + +void +goodix_receive_success (FpDevice *dev, guint8 *data, guint16 length, + gpointer user_data, GError *error) +{ + g_autofree GoodixCallbackInfo *cb_info = user_data; + GoodixSuccessCallback callback = (GoodixSuccessCallback) cb_info->callback; + + if (error) + { + callback (dev, FALSE, cb_info->user_data, error); + return; + } + + if (length != sizeof (guint8) * 2) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid success reply length: %d", length); + callback (dev, FALSE, cb_info->user_data, error); + return; + } + + callback (dev, data[0] == 0x00 ? FALSE : TRUE, cb_info->user_data, NULL); +} + +void +goodix_receive_reset (FpDevice *dev, guint8 *data, guint16 length, + gpointer user_data, GError *error) +{ + g_autofree GoodixCallbackInfo *cb_info = user_data; + GoodixResetCallback callback = (GoodixResetCallback) cb_info->callback; + + if (error) + { + callback (dev, FALSE, 0, cb_info->user_data, error); + return; + } + + if (length != sizeof (guint8) + sizeof (guint16)) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid reset reply length: %d", length); + callback (dev, FALSE, 0, cb_info->user_data, error); + return; + } + + callback (dev, data[0] == 0x00 ? FALSE : TRUE, + GUINT16_FROM_LE (*(guint16 *) (data + sizeof (guint8))), // TODO + cb_info->user_data, NULL); +} + +void +goodix_receive_preset_psk_read (FpDevice *dev, guint8 *data, guint16 length, + gpointer user_data, GError *error) +{ + guint32 psk_len; + g_autofree GoodixCallbackInfo *cb_info = user_data; + GoodixPresetPskReadCallback callback = + (GoodixPresetPskReadCallback) cb_info->callback; + + if (error) + { + callback (dev, FALSE, 0x00000000, NULL, 0, cb_info->user_data, error); + return; + } + + if (length < sizeof (guint8)) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid preset PSK read reply length: %d", length); + callback (dev, FALSE, 0x00000000, NULL, 0, cb_info->user_data, error); + return; + } + + if (data[0] != 0x00) + { + callback (dev, FALSE, 0x00000000, NULL, 0, cb_info->user_data, NULL); + return; + } + + if (length < sizeof (guint8) + sizeof (GoodixPresetPsk)) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid preset PSK read reply length: %d", length); + callback (dev, FALSE, 0x00000000, NULL, 0, cb_info->user_data, error); + return; + } + + psk_len = + GUINT32_FROM_LE (((GoodixPresetPsk *) (data + sizeof (guint8)))->length); + + if (length < psk_len + sizeof (guint8) + sizeof (GoodixPresetPsk)) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid preset PSK read reply length: %d", length); + callback (dev, FALSE, 0x00000000, NULL, 0, cb_info->user_data, error); + return; + } + + callback (dev, TRUE, + GUINT32_FROM_LE (((GoodixPresetPsk *) (data + sizeof (guint8)))->flags), + data + sizeof (guint8) + sizeof (GoodixPresetPsk), psk_len, + cb_info->user_data, NULL); +} + +void +goodix_receive_preset_psk_write (FpDevice *dev, guint8 *data, + guint16 length, gpointer user_data, + GError *error) +{ + g_autofree GoodixCallbackInfo *cb_info = user_data; + GoodixSuccessCallback callback = (GoodixSuccessCallback) cb_info->callback; + + if (error) + { + callback (dev, FALSE, cb_info->user_data, error); + return; + } + + if (length < sizeof (guint8)) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid preset PSK write reply length: %d", length); + callback (dev, FALSE, cb_info->user_data, error); + return; + } + + callback (dev, data[0] == 0x00 ? TRUE : FALSE, cb_info->user_data, NULL); +} + +void +goodix_receive_firmware_version (FpDevice *dev, guint8 *data, + guint16 length, gpointer user_data, + GError *error) +{ + g_autofree gchar *payload = g_malloc (length + sizeof (gchar)); + g_autofree GoodixCallbackInfo *cb_info = user_data; + GoodixFirmwareVersionCallback callback = + (GoodixFirmwareVersionCallback) cb_info->callback; + + if (error) + { + callback (dev, NULL, cb_info->user_data, error); + return; + } + + memcpy (payload, data, length); + + // Some device send the firmware without the null terminator + payload[length] = 0x00; + + callback (dev, payload, cb_info->user_data, NULL); +} + +void +goodix_receive_ack (FpDevice *dev, guint8 *data, guint16 length, + gpointer user_data, GError *error) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + GoodixAck *ack = (GoodixAck *) data; + guint8 cmd; + + if (length != sizeof (GoodixAck)) + { + fp_warn ("Invalid ACK length: %d", length); + return; + } + + if (!ack->always_true) + { + // Warn about error. + fp_warn ("Invalid ACK flags: 0x%02x", data[sizeof (guint8)]); + return; + } + + cmd = ack->cmd; + + if (ack->has_no_config) + fp_warn ("MCU has no config"); + + if (priv->cmd != cmd) + { + fp_warn ("Invalid ACK command: 0x%02x", cmd); + return; + } + + if (!priv->ack) + { + fp_warn ("Didn't excpect an ACK for command: 0x%02x", priv->cmd); + return; + } + + if (!priv->reply) + { + G_DEBUG_HERE (); + goodix_receive_done (dev, NULL, 0, NULL); + return; + } + + priv->ack = FALSE; +} + +void +goodix_receive_protocol (FpDevice *dev, guint8 *data, guint32 length) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + guint8 cmd; + g_autofree guint8 *payload = NULL; + guint16 payload_len; + gboolean valid_checksum, valid_null_checksum; // TODO implement checksum. + + if (!goodix_decode_protocol (data, length, &cmd, &payload, &payload_len, + &valid_checksum, &valid_null_checksum)) + { + fp_err ("Incomplete, size: %d", length); + // Protocol is not full, we still need data. + // TODO implement protocol assembling. + return; + } + + if (cmd == GOODIX_CMD_ACK) + { + fp_dbg ("got ack"); + goodix_receive_ack (dev, payload, payload_len, NULL, NULL); + return; + } + + if (priv->cmd != cmd) + { + fp_warn ("Invalid protocol command: 0x%02x", cmd); + return; + } + + if (!priv->reply) + { + fp_warn ("Didn't excpect a reply for command: 0x%02x", priv->cmd); + return; + } + + if (priv->ack) + fp_warn ("Didn't got ACK for command: 0x%02x", priv->cmd); + + goodix_receive_done (dev, payload, payload_len, NULL); +} + +void +goodix_receive_pack (FpDevice *dev, guint8 *data, guint32 length) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + guint8 flags; + g_autofree guint8 *payload = NULL; + guint16 payload_len; + gboolean valid_checksum; // TODO implement checksum. + + priv->data = g_realloc (priv->data, priv->length + length); + memcpy (priv->data + priv->length, data, length); + priv->length += length; + + if (!goodix_decode_pack (priv->data, priv->length, &flags, &payload, + &payload_len, &valid_checksum)) + { + // Packet is not full, we still need data. + fp_dbg ("not full packet"); + return; + } + + switch (flags) + { + case GOODIX_FLAGS_MSG_PROTOCOL: + fp_dbg ("Got protocol msg"); + goodix_receive_protocol (dev, payload, payload_len); + break; + + case GOODIX_FLAGS_TLS: + case GOODIX_FLAGS_TLS_DATA: + fp_dbg ("Got TLS msg (0x%02x, %u bytes)", flags, payload_len); + if (priv->cmd == GOODIX_CMD_MCU_GET_IMAGE || + priv->cmd == GOODIX_CMD_REQUEST_TLS_CONNECTION || + (priv->reply && priv->callback != NULL && priv->cmd == 0)) + goodix_receive_done (dev, payload, payload_len, NULL); + else + fp_dbg ("Discarding stale TLS msg (0x%02x, len %u) while waiting for cmd 0x%02x", + flags, payload_len, priv->cmd); + break; + + default: + fp_warn ("Unknown flags: 0x%02x", flags); + break; + } + + g_clear_pointer (&priv->data, g_free); + priv->length = 0; +} + +void +goodix_receive_data_cb (FpiUsbTransfer *transfer, FpDevice *dev, + gpointer user_data, GError *error) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + if (g_cancellable_is_cancelled (priv->transfer_cancel_tkn) || + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + fp_dbg ("transfer cancelled, aborting read loop..."); + if (error) + g_error_free (error); + return; + } + if (error) + { + // Warn about error and free it. + fp_warn ("Receive data error: %s", error->message); + g_error_free (error); + + // Retry receiving data and return. + goodix_receive_data (dev); + return; + } + + goodix_receive_pack (dev, transfer->buffer, transfer->actual_length); + + goodix_receive_data (dev); +} + +void +goodix_receive_timeout_cb (FpDevice *dev, gpointer user_data) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + GError *error = NULL; + + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, + "Command timed out: 0x%02x", priv->cmd); + goodix_receive_done (dev, NULL, 0, error); +} + +void +goodix_start_read_loop (FpDevice *dev) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + if (priv->inited) + return; + + if (g_cancellable_is_cancelled (priv->transfer_cancel_tkn)) + g_cancellable_reset (priv->transfer_cancel_tkn); + + priv->inited = TRUE; + g_clear_pointer (&priv->data, g_free); + priv->length = 0; + + goodix_receive_data (dev); +} + +void +goodix_stop_read_loop (FpDevice *dev) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + fp_dbg ("Stopping read loop"); + if (priv->transfer_cancel_tkn) + g_cancellable_cancel (priv->transfer_cancel_tkn); + + priv->inited = FALSE; + g_clear_pointer (&priv->data, g_free); + priv->length = 0; +} + +void +goodix_receive_data (FpDevice *dev) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsClass *class = FPI_DEVICE_GOODIXTLS_GET_CLASS (self); + FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + transfer->short_is_error = FALSE; + + fpi_usb_transfer_fill_bulk (transfer, class->ep_in, + GOODIX_EP_IN_MAX_BUF_SIZE); + + fpi_usb_transfer_submit (transfer, 0, priv->transfer_cancel_tkn, + goodix_receive_data_cb, NULL); +} + +// ---- GOODIX RECEIVE SECTION END ---- + +// ----------------------------------------------------------------------------- + +// ---- GOODIX SEND SECTION START ---- + +gboolean +goodix_send_data (FpDevice *dev, guint8 *data, guint32 length, + GDestroyNotify free_func, GError **error) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsClass *class = FPI_DEVICE_GOODIXTLS_GET_CLASS (self); + + for (guint32 i = 0; i < length; i += GOODIX_EP_OUT_MAX_BUF_SIZE) + { + FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); + transfer->short_is_error = TRUE; + + fpi_usb_transfer_fill_bulk_full (transfer, class->ep_out, data + i, + GOODIX_EP_OUT_MAX_BUF_SIZE, NULL); + + if (!fpi_usb_transfer_submit_sync (transfer, GOODIX_TIMEOUT, + error)) + { + if (free_func) + free_func (data); + fpi_usb_transfer_unref (transfer); + return FALSE; + } + fpi_usb_transfer_unref (transfer); + } + + if (free_func) + free_func (data); + return TRUE; +} + +gboolean +goodix_send_pack (FpDevice *dev, guint8 flags, guint8 *payload, + guint16 length, GDestroyNotify free_func, + GError **error) +{ + guint8 *data; + guint32 data_len; + + goodix_encode_pack (flags, payload, length, TRUE, &data, &data_len); + if (free_func) + free_func (payload); + + return goodix_send_data (dev, data, data_len, g_free, error); +} + +void +goodix_send_protocol ( + FpDevice *dev, guint8 cmd, const guint8 *payload, guint16 length, + GDestroyNotify free_func, gboolean calc_checksum, guint timeout_ms, + gboolean reply, GoodixCmdCallback callback, gpointer user_data) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + GError *error = NULL; + guint8 *data; + guint32 data_len; + + if (priv->ack || priv->reply || priv->timeout) + { + // A command is already running. + fp_warn ("A command is already running: 0x%02x", priv->cmd); + if (free_func) + free_func ((void *) payload); + // ponytail: fail loudly so the waiting SSM aborts instead of hanging + GError *collision_error = + g_error_new (G_IO_ERROR, G_IO_ERROR_BUSY, + "A command is already running: 0x%02x", priv->cmd); + goodix_receive_done (dev, NULL, 0, collision_error); + return; + } + + fp_dbg ("Running command: 0x%02x", cmd); + + if (timeout_ms) + priv->timeout = fpi_device_add_timeout ( + dev, timeout_ms, goodix_receive_timeout_cb, NULL, NULL); + priv->cmd = cmd; + priv->ack = TRUE; + priv->reply = reply; + priv->callback = callback; + priv->user_data = user_data; + + goodix_encode_protocol (cmd, payload, length, calc_checksum, FALSE, + &data, &data_len); + if (free_func) + free_func ((void *) payload); + + if (!goodix_send_pack (dev, GOODIX_FLAGS_MSG_PROTOCOL, data, data_len, + g_free, &error)) + { + goodix_receive_done (dev, NULL, 0, error); + return; + } + ; +} +void +goodix_send_nop (FpDevice *dev, GoodixNoneCallback callback, + gpointer user_data) +{ + GoodixNop payload = {.unknown = 0x00000000}; + GoodixCallbackInfo *cb_info; + + /* ponytail: flush, not a handshake — silence is success (see tolerant + receiver); a real ACK is still validated when one arrives. */ + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_NOP, (guint8 *) &payload, + sizeof (payload), NULL, FALSE, GOODIX_NOP_TIMEOUT, FALSE, + goodix_receive_none_tolerant, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_NOP, (guint8 *) &payload, sizeof (payload), + NULL, FALSE, GOODIX_NOP_TIMEOUT, FALSE, NULL, NULL); +} + +guint8 goodix5e0a_capture_payload[10] = {0x05, 0x00, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00}; + +void +goodix_send_mcu_get_image (FpDevice *dev, GoodixImageCallback callback, + gpointer user_data) +{ + GoodixCallbackInfo *cb_info; + GoodixDefault payload_default = {.unused_flags = 0x01}; + guint8 *payload = (guint8 *) &payload_default; + guint16 len = sizeof (payload_default); + + if (g_strcmp0 (fp_device_get_driver (dev), "goodixtls5e0a") == 0) + { + payload = goodix5e0a_capture_payload; + len = sizeof (goodix5e0a_capture_payload); + } + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_MCU_GET_IMAGE, payload, + len, NULL, TRUE, GOODIX_TIMEOUT, TRUE, + goodix_receive_default, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_MCU_GET_IMAGE, payload, + len, NULL, TRUE, GOODIX_TIMEOUT, TRUE, + NULL, NULL); +} + +void +goodix_send_mcu_switch_to_fdt_down (FpDevice *dev, const guint8 *mode, guint16 length, + GDestroyNotify free_func, + GoodixDefaultCallback callback, + gpointer user_data) +{ + GoodixCallbackInfo *cb_info = NULL; + GoodixDefaultCallback cb = NULL; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + cb = goodix_receive_default; + } + + if (mode && length > 0 && mode[0] == 0x01) + { + guint8 * payload = malloc (sizeof (guint8) * (length + 1)); + memcpy (payload + 1, mode, length); + payload[0] = 0xc; + if (free_func) + free_func ((void *) mode); + goodix_send_protocol (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, payload, length + 1, + free, TRUE, 0, TRUE, cb, cb_info); + } + else + { + goodix_send_protocol (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, mode, length, + free_func, TRUE, 0, TRUE, cb, cb_info); + } +} + +void +goodix_send_mcu_switch_to_fdt_up (FpDevice *dev, const guint8 *mode, guint16 length, + GDestroyNotify free_func, + GoodixDefaultCallback callback, + gpointer user_data) +{ + GoodixCallbackInfo *cb_info = NULL; + GoodixDefaultCallback cb = NULL; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + cb = goodix_receive_default; + } + + if (mode && length > 0 && mode[0] == 0x01) + { + guint8 * payload = malloc (sizeof (guint8) * (length + 1)); + memcpy (payload + 1, mode, length); + payload[0] = 0xe; + if (free_func) + free_func ((void *) mode); + goodix_send_protocol (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_UP, payload, length + 1, + free, TRUE, 0, TRUE, cb, cb_info); + } + else + { + goodix_send_protocol (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_UP, mode, length, + free_func, TRUE, 0, TRUE, cb, cb_info); + } +} + +void +goodix_send_mcu_switch_to_fdt_mode (FpDevice *dev, const guint8 *mode, + guint16 length, GDestroyNotify free_func, + GoodixNoneCallback callback, + gpointer user_data) +{ + GoodixCallbackInfo *cb_info = NULL; + GoodixCmdCallback cb = NULL; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + cb = goodix_receive_none; + } + + goodix_send_protocol (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_MODE, mode, length, + free_func, TRUE, GOODIX_TIMEOUT, FALSE, cb, + cb_info); + +} + +void +goodix_send_nav_0 (FpDevice *dev, GoodixDefaultCallback callback, + gpointer user_data) +{ + GoodixDefault payload = {.unused_flags = 0x01}; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_NAV_0, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, + goodix_receive_default, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_NAV_0, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, NULL, + NULL); +} + +void +goodix_send_mcu_switch_to_idle_mode (FpDevice *dev, guint8 sleep_time, + GoodixNoneCallback callback, + gpointer user_data) +{ + GoodixMcuSwitchToIdleMode payload = {.sleep_time = sleep_time}; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_MCU_SWITCH_TO_IDLE_MODE, + (guint8 *) &payload, sizeof (payload), NULL, TRUE, + GOODIX_TIMEOUT, FALSE, goodix_receive_none, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_MCU_SWITCH_TO_IDLE_MODE, + (guint8 *) &payload, sizeof (payload), NULL, TRUE, + GOODIX_TIMEOUT, FALSE, NULL, NULL); +} + +void +goodix_send_write_sensor_register (FpDevice *dev, guint16 address, + guint16 value, + GoodixNoneCallback callback, + gpointer user_data) +{ + // Only support one address and one value + + GoodixWriteSensorRegister payload = {.multiples = FALSE, + .address = GUINT16_TO_LE (address), + .value = GUINT16_TO_LE (value)}; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_WRITE_SENSOR_REGISTER, + (guint8 *) &payload, sizeof (payload), NULL, TRUE, + GOODIX_TIMEOUT, FALSE, goodix_receive_none, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_WRITE_SENSOR_REGISTER, + (guint8 *) &payload, sizeof (payload), NULL, TRUE, + GOODIX_TIMEOUT, FALSE, NULL, NULL); +} + +void +goodix_send_read_sensor_register (FpDevice *dev, guint16 address, + guint8 length, + GoodixDefaultCallback callback, + gpointer user_data) +{ + // Only support one address + + GoodixReadSensorRegister payload = { + .multiples = FALSE, .address = GUINT16_TO_LE (address), .length = length + }; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_READ_SENSOR_REGISTER, + (guint8 *) &payload, sizeof (payload), NULL, TRUE, + GOODIX_TIMEOUT, TRUE, goodix_receive_default, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_READ_SENSOR_REGISTER, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, NULL, + NULL); +} + +void +goodix_send_upload_config_mcu (FpDevice *dev, guint8 *config, + guint16 length, GDestroyNotify free_func, + GoodixSuccessCallback callback, + gpointer user_data) +{ + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_UPLOAD_CONFIG_MCU, config, length, + free_func, TRUE, GOODIX_TIMEOUT, TRUE, + goodix_receive_success, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_UPLOAD_CONFIG_MCU, config, length, + free_func, TRUE, GOODIX_TIMEOUT, TRUE, NULL, NULL); +} + +void +goodix_send_set_powerdown_scan_frequency (FpDevice *dev, + guint16 powerdown_scan_frequency, + GoodixSuccessCallback callback, + gpointer user_data) +{ + GoodixSetPowerdownScanFrequency payload = { + .powerdown_scan_frequency = GUINT16_TO_LE (powerdown_scan_frequency) + }; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_SET_POWERDOWN_SCAN_FREQUENCY, + (guint8 *) &payload, sizeof (payload), NULL, TRUE, + GOODIX_TIMEOUT, TRUE, goodix_receive_success, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_SET_POWERDOWN_SCAN_FREQUENCY, + (guint8 *) &payload, sizeof (payload), NULL, TRUE, + GOODIX_TIMEOUT, TRUE, NULL, NULL); +} + +void +goodix_send_enable_chip (FpDevice *dev, gboolean enable, + GoodixNoneCallback callback, gpointer user_data) +{ + GoodixEnableChip payload = {.enable = enable ? TRUE : FALSE}; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_ENABLE_CHIP, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, FALSE, + goodix_receive_none, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_ENABLE_CHIP, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, FALSE, NULL, + NULL); +} + +void +goodix_send_reset (FpDevice *dev, gboolean reset_sensor, guint8 sleep_time, + GoodixResetCallback callback, gpointer user_data) +{ + // Only support reset sensor + + GoodixReset payload = {.soft_reset_mcu = FALSE, + .reset_sensor = reset_sensor ? TRUE : FALSE, + .sleep_time = sleep_time}; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_RESET, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, + goodix_receive_reset, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_RESET, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, NULL, + NULL); +} + +void +goodix_send_query_firmware_version (FpDevice *dev, + GoodixFirmwareVersionCallback callback, + gpointer user_data) +{ + GoodixNone payload = {}; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_FIRMWARE_VERSION, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, + goodix_receive_firmware_version, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_FIRMWARE_VERSION, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, NULL, + NULL); +} + +void +goodix_send_query_mcu_state (FpDevice *dev, GoodixNoneCallback callback, + gpointer user_data) +{ + GoodixQueryMcuState payload = {.unused_flags = 0x55}; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_QUERY_MCU_STATE, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, FALSE, + goodix_receive_none, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_QUERY_MCU_STATE, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, FALSE, NULL, + NULL); +} + +void +goodix_send_request_tls_connection (FpDevice *dev, + GoodixDefaultCallback callback, + gpointer user_data) +{ + GoodixNone payload = {}; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_REQUEST_TLS_CONNECTION, + (guint8 *) &payload, sizeof (payload), NULL, TRUE, 0, + TRUE, goodix_receive_default, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_REQUEST_TLS_CONNECTION, + (guint8 *) &payload, sizeof (payload), NULL, TRUE, + GOODIX_TIMEOUT, TRUE, NULL, NULL); +} + +void +goodix_send_tls_successfully_established (FpDevice *dev, + GoodixNoneCallback callback, + gpointer user_data) +{ + GoodixNone payload = {}; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + // special case: timeout needs to be at least 10ms and it will always timeout for some reason + // todo: work out why it always times out for this but not the python driver + + goodix_send_protocol (dev, GOODIX_CMD_TLS_SUCCESSFULLY_ESTABLISHED, + (guint8 *) &payload, sizeof (payload), NULL, TRUE, + 2000, TRUE, goodix_receive_none, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_TLS_SUCCESSFULLY_ESTABLISHED, + (guint8 *) &payload, sizeof (payload), NULL, TRUE, + 2000, TRUE, NULL, NULL); +} + +void +goodix_send_set_drv_state (FpDevice *dev, GoodixNoneCallback cb, + gpointer ud) +{ + // ponytail: reuse 2-byte helper for the 01 00 payload (goodix.py:611-622) + GoodixDefault payload = {.unused_flags = 0x01}; + GoodixCallbackInfo *cb_info; + + if (cb) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (cb); + cb_info->user_data = ud; + + goodix_send_protocol (dev, GOODIX_CMD_SET_DRV_STATE, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, FALSE, + goodix_receive_none, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_SET_DRV_STATE, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, FALSE, + NULL, NULL); +} + +void +goodix_send_mcu_get_pov_image (FpDevice *dev, GoodixDefaultCallback cb, + gpointer ud) +{ + GoodixNone payload = {}; + GoodixCallbackInfo *cb_info; + + if (cb) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (cb); + cb_info->user_data = ud; + + goodix_send_protocol (dev, GOODIX_CMD_MCU_GET_POV_IMAGE, + (guint8 *) &payload, sizeof (payload), NULL, TRUE, + GOODIX_TIMEOUT, TRUE, goodix_receive_default, + cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_MCU_GET_POV_IMAGE, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, + NULL, NULL); +} + +void +goodix_send_set_pov_config (FpDevice *dev, const guint8 *cfg, guint16 len, + GDestroyNotify ff, GoodixNoneCallback cb, + gpointer ud) +{ + GoodixCallbackInfo *cb_info = NULL; + GoodixCmdCallback tramp = NULL; + + if (cb) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (cb); + cb_info->user_data = ud; + tramp = goodix_receive_none; + } + + goodix_send_protocol (dev, GOODIX_CMD_SET_POV_CONFIG, cfg, len, ff, TRUE, + GOODIX_TIMEOUT, FALSE, tramp, cb_info); +} + +void +goodix_send_read_otp (FpDevice *dev, GoodixDefaultCallback callback, + gpointer user_data) +{ + GoodixNone payload = {}; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_READ_OTP, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, + goodix_receive_default, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_READ_OTP, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, + NULL, NULL); +} + +void +goodix_send_preset_psk_write (FpDevice *dev, guint32 flags, guint8 *psk, + guint16 length, GDestroyNotify free_func, + GoodixSuccessCallback callback, + gpointer user_data) +{ + // Only support one flags, one payload and one length + + guint8 *payload = g_malloc (sizeof (GoodixPresetPsk) + length); + GoodixPresetPsk *preset_psk = (GoodixPresetPsk *) payload; + GoodixCallbackInfo *cb_info; + + preset_psk->flags = GUINT32_TO_LE (flags); + preset_psk->length = GUINT32_TO_LE (length); + memcpy (payload + sizeof (GoodixPresetPsk), psk, length); + if (free_func) + free_func (psk); + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_PRESET_PSK_WRITE, payload, + sizeof (GoodixPresetPsk) + length, g_free, TRUE, GOODIX_TIMEOUT, + TRUE, goodix_receive_preset_psk_write, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_PRESET_PSK_WRITE, payload, + sizeof (GoodixPresetPsk) + length, g_free, TRUE, GOODIX_TIMEOUT, + TRUE, NULL, NULL); +} + +void +goodix_send_preset_psk_read (FpDevice *dev, guint32 flags, guint16 length, + GoodixPresetPskReadCallback callback, + gpointer user_data) +{ + GoodixPresetPsk payload = {.flags = GUINT32_TO_LE (flags), + .length = GUINT32_TO_LE (length)}; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_PRESET_PSK_READ, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, + goodix_receive_preset_psk_read, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_PRESET_PSK_READ, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, NULL, + NULL); +} + +// ---- GOODIX SEND SECTION END ---- + +// ----------------------------------------------------------------------------- + +// ---- DEV SECTION START ---- + +gboolean +goodix_dev_init (FpDevice *dev, GError **error) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsClass *class = FPI_DEVICE_GOODIXTLS_GET_CLASS (self); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + priv->timeout = NULL; + priv->ack = FALSE; + priv->reply = FALSE; + priv->callback = NULL; + priv->user_data = NULL; + priv->data = NULL; + priv->length = 0; + priv->transfer_cancel_tkn = g_cancellable_new (); + + g_usb_device_reset (fpi_device_get_usb_device (dev), NULL); + + return g_usb_device_claim_interface (fpi_device_get_usb_device (dev), + class->interface, 0, error); +} +void +goodix_reset_state (FpDevice *dev) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + if (priv->timeout) + g_clear_pointer (&priv->timeout, g_source_destroy); + priv->ack = FALSE; + priv->reply = FALSE; + priv->cmd = 0; + priv->callback = NULL; + priv->user_data = NULL; + g_clear_pointer (&priv->data, g_free); + priv->length = 0; +} + +/* Stale-activation guard counter (live paths untouched). */ +guint +goodix_activation_gen_get (FpDevice *dev) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + return priv->activation_gen; +} + +guint +goodix_activation_gen_bump (FpDevice *dev) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + return ++priv->activation_gen; +} + +gboolean +goodix_dev_deinit (FpDevice *dev, GError **error) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsClass *class = FPI_DEVICE_GOODIXTLS_GET_CLASS (self); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + /* Teardown entry: orphan any in-flight TLS activation. */ + goodix_activation_gen_bump (dev); + + if (priv->timeout) + g_source_destroy (priv->timeout); + g_free (priv->data); + g_cancellable_cancel (priv->transfer_cancel_tkn); + goodix_shutdown_tls (dev, error); + + goodix_reset_state (dev); + priv->inited = FALSE; + + return g_usb_device_release_interface (fpi_device_get_usb_device (dev), + class->interface, 0, error); +} + +// ---- DEV SECTION END ---- + +// ----------------------------------------------------------------------------- + +// ---- TLS SECTION START ---- + +void +goodix_read_tls (FpDevice *dev, GoodixTlsCallback callback, + gpointer user_data) +{ + + fp_dbg ("goodix_read_tls()"); + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + priv->callback = callback; + priv->user_data = user_data; + priv->reply = TRUE; + priv->cmd = 0; +} + +enum tls_states { + TLS_SERVER_INIT, + TLS_SERVER_HANDSHAKE_INIT, + TLS_NUM_STATES, +}; + + +static void +on_goodix_tls_read_handshake (FpDevice *dev, guint8 *data, + guint16 length, gpointer user_data, + GError *error) +{ + // goodix_tls_handshake_state* state = (goodix_tls_handshake_state*) + // user_data; + FpiSsm *ssm = user_data; + + if (error) + { + fpi_ssm_mark_failed (ssm, error); + return; + } + FpiDeviceGoodixTls *self = + FPI_DEVICE_GOODIXTLS (fpi_ssm_get_data (user_data)); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + int sent = goodix_tls_client_write (priv->tls_hop, data, length); + + if (sent < 0) + { + fpi_ssm_mark_failed (ssm, g_error_new (g_io_error_quark (), sent, + "failed to sent data to " + "tls server")); + return; + } + fpi_ssm_next_state (ssm); +} + +enum goodix_tls_handshake_stages { + TLS_HANDSHAKE_STAGE_HELLO_S, + TLS_HANDSHAKE_STAGE_KH_EXCHANGE, + TLS_HANDSHAKE_STAGE_CHANGE_CIPHER_C, + TLS_HANDSHAKE_STAGE_HANDSHAKE_C, + TLS_HANDSHAKE_STAGE_CHANGE_CIPHER_S, + + TLS_HANDSHAKE_STAGE_NUM, +}; + +static void +on_tls_successfully_established (FpDevice *dev, gpointer user_data, + GError *error) +{ + fp_dbg ("HANDSHAKE DONE"); + FpiDeviceGoodixTls * self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate * priv = + fpi_device_goodixtls_get_instance_private (self); + ((GoodixNoneCallback) priv->tls_ready_callback->callback)( + dev, priv->tls_ready_callback->user_data, NULL); + g_clear_pointer (&priv->tls_ready_callback, g_free); +} +static void +tls_handshake_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + if (error) + { + fp_err ("failed to do tls handshake: %s (code: %d)", error->message, + error->code); + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + if (priv->tls_ready_callback) + { + ((GoodixNoneCallback) priv->tls_ready_callback->callback)( + dev, priv->tls_ready_callback->user_data, error); + g_clear_pointer (&priv->tls_ready_callback, g_free); + } + return; + } + goodix_send_tls_successfully_established ( + dev, on_tls_successfully_established, NULL); +} + +static void +tls_handshake_run (FpiSsm *ssm, FpDevice *dev) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + int stage = fpi_ssm_get_cur_state (ssm); + + if (stage == TLS_HANDSHAKE_STAGE_HELLO_S) + { + guint8 buff[1024]; + int size = goodix_tls_client_read (priv->tls_hop, buff, sizeof (buff)); + if (size < 0) + { + fpi_ssm_mark_failed (ssm, g_error_new (g_io_error_quark (), size, + "failed to read tls server " + "hello")); + return; + } + GError *err = NULL; + if (!goodix_send_pack (dev, GOODIX_FLAGS_TLS, buff, size, NULL, &err)) + { + fpi_ssm_mark_failed (ssm, err); + return; + } + fpi_ssm_next_state (ssm); + } + else if (stage < TLS_HANDSHAKE_STAGE_CHANGE_CIPHER_S) + { + // Still proxying from hardware + fpi_ssm_set_data (ssm, dev, NULL); + goodix_read_tls (dev, on_goodix_tls_read_handshake, ssm); + } + else if (stage == TLS_HANDSHAKE_STAGE_CHANGE_CIPHER_S) + { + fp_dbg ("Reading to proxy back"); + guint8 buff[1024]; + int size = goodix_tls_client_read (priv->tls_hop, buff, sizeof (buff)); + if (size < 0) + { + fpi_ssm_mark_failed (ssm, g_error_new (g_io_error_quark (), size, + "failed to read server " + "handshake")); + + return; + } + GError *err = NULL; + if (!goodix_send_pack (dev, GOODIX_FLAGS_TLS, buff, size, NULL, &err)) + { + fpi_ssm_mark_failed (ssm, err); + return; + } + fpi_ssm_next_state (ssm); + } +} + +static void +do_tls_handshake (FpDevice *dev) +{ + fpi_ssm_start (fpi_ssm_new (dev, tls_handshake_run, TLS_HANDSHAKE_STAGE_NUM), + tls_handshake_done); +} + +static void +on_goodix_request_tls_connection (FpDevice *dev, guint8 *data, + guint16 length, gpointer user_data, + GError *error) +{ + if (error) + { + fp_err ("failed to get tls handshake: %s", error->message); + goodix_send_tls_successfully_established (FP_DEVICE (dev), NULL, NULL); + return; + } + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (user_data); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + goodix_tls_client_write (priv->tls_hop, data, length); + + do_tls_handshake (dev); +} + +static void +goodix_tls_ready (GoodixTlsServer *server, GError *err, gpointer dev) +{ + if (err) + { + fp_err ("failed to init tls server: %s, code: %d", err->message, + err->code); + return; + } + goodix_send_request_tls_connection (FP_DEVICE (dev), + on_goodix_request_tls_connection, dev); +} + +void +goodix_tls_init (FpDevice *dev, GoodixNoneCallback callback, gpointer user_data) +{ + fp_dbg ("Starting up goodix tls server"); + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + g_assert (priv->tls_hop == NULL); + priv->tls_hop = g_new0 (GoodixTlsServer, 1); + + if (!priv->tls_ready_callback) + priv->tls_ready_callback = g_new0 (GoodixCallbackInfo, 1); + priv->tls_ready_callback->callback = G_CALLBACK (callback); + priv->tls_ready_callback->user_data = user_data; + GoodixTlsServer *s = priv->tls_hop; + s->user_data = self; + GError *err = NULL; + if (!goodix_tls_server_init (priv->tls_hop, &err)) + { + fp_err ("failed to init tls server, error: %s, code: %d", err->message, + err->code); + return; + } + + goodix_tls_ready (s, err, self); +} + +gboolean +goodix_shutdown_tls (FpDevice *dev, GError **error) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + if (priv->tls_hop) + { + gboolean rs = goodix_tls_server_deinit (priv->tls_hop, error); + g_free (priv->tls_hop); + priv->tls_hop = NULL; + return rs; + } + return TRUE; +} +static void +goodix_tls_ready_image_handler (FpDevice *dev, guint8 *data, + guint16 length, gpointer user_data, + GError *error) +{ + GoodixCallbackInfo *cb_info = user_data; + GoodixImageCallback callback = (GoodixImageCallback) cb_info->callback; + + if (error) + { + callback (dev, NULL, 0, cb_info->user_data, error); + g_free (cb_info); + return; + } + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + guint8 *tls_data = data; + guint16 tls_len = length; + + if (length > 9 && data[0] == 0x00 && data[1] == 0x20) + { + tls_data = data + 9; + tls_len = length - 9; + } + else if (length > 5 && data[0] != 0x17) + { + for (guint16 i = 0; i + 5 < length; i++) + { + if (data[i] == 0x17 && data[i + 1] == 0x03 && data[i + 2] == 0x03) + { + tls_data = data + i; + tls_len = length - i; + break; + } + } + } + + goodix_tls_client_write (priv->tls_hop, tls_data, tls_len); + + guint32 size = 65535; + guint8 *buff = malloc (size); + GError *err = NULL; + int read_size = goodix_tls_server_read (priv->tls_hop, buff, size, &err); + + if (read_size <= 0) + { + callback (dev, NULL, 0, cb_info->user_data, err); + free (buff); + g_free (cb_info); + return; + } + + callback (dev, buff, read_size, cb_info->user_data, NULL); + free (buff); + g_free (cb_info); +} + +void +goodix_tls_read_image (FpDevice *dev, GoodixImageCallback callback, + gpointer user_data) +{ + g_assert (callback); + GoodixCallbackInfo *cb_info = malloc (sizeof (GoodixCallbackInfo)); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_mcu_get_image (dev, goodix_tls_ready_image_handler, cb_info); +} + +// ---- TLS SECTION END ---- + +static void +fpi_device_goodixtls_init (FpiDeviceGoodixTls *self) +{ +} + +static void +fpi_device_goodixtls_class_init (FpiDeviceGoodixTlsClass *class) +{ +} diff --git a/libfprint/drivers/goodixtls/goodix.h b/libfprint/drivers/goodixtls/goodix.h new file mode 100644 index 000000000..d8c496bab --- /dev/null +++ b/libfprint/drivers/goodixtls/goodix.h @@ -0,0 +1,608 @@ +// Goodix Tls driver for libfprint + +// Copyright (C) 2021 Alexander Meiler +// Copyright (C) 2021 Matthieu CHARETTE +// Copyright (C) 2021 Natasha England-Elbro + +// 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 + +#pragma once + +#include "drivers_api.h" + +// 1 seconds USB timeout +#define GOODIX_TIMEOUT (1000) + +/* ponytail: NOP is a flush — the MCU is routinely silent on it (reference + tolerates no-reply as success), so it gets a short window, not the full. */ +#define GOODIX_NOP_TIMEOUT (200) + +G_DECLARE_DERIVABLE_TYPE (FpiDeviceGoodixTls, fpi_device_goodixtls, FPI, + DEVICE_GOODIXTLS, FpImageDevice) + +#define FPI_TYPE_DEVICE_GOODIXTLS (fpi_device_goodixtls_get_type ()) + +struct _FpiDeviceGoodixTlsClass +{ + FpImageDeviceClass parent; + + gint interface; + guint8 ep_in; + guint8 ep_out; +}; + +typedef struct __attribute__((__packed__)) _GoodixCallbackInfo +{ + GCallback callback; + gpointer user_data; +} GoodixCallbackInfo; + +typedef void (*GoodixCmdCallback)(FpDevice *dev, + guint8 *data, + guint16 length, + gpointer user_data, + GError *error); + +typedef void (*GoodixFirmwareVersionCallback)(FpDevice *dev, + gchar *firmware, + gpointer user_data, + GError *error); + +typedef void (*GoodixPresetPskReadCallback)(FpDevice *dev, + gboolean success, + guint32 flags, + guint8 *psk, + guint16 length, + gpointer user_data, + GError *error); + +typedef void (*GoodixSuccessCallback)(FpDevice *dev, + gboolean success, + gpointer user_data, + GError *error); + +typedef void (*GoodixResetCallback)(FpDevice *dev, + gboolean success, + guint16 number, + gpointer user_data, + GError *error); + +typedef void (*GoodixNoneCallback)(FpDevice *dev, + gpointer user_data, + GError *error); + +typedef void (*GoodixDefaultCallback)(FpDevice *dev, + guint8 *data, + guint16 length, + gpointer user_data, + GError *error); +typedef GoodixDefaultCallback GoodixTlsCallback; + +typedef void (*GoodixImageCallback)(FpDevice *dev, + guint8 *data, + guint16 length, + gpointer user_data, + GError *error); + +gchar *data_to_str (guint8 *data, + guint32 length); + +// ---- GOODIX RECEIVE SECTION START ---- + +/** + * @defgroup goodixrecv Goodix receive functions + * These functions are callbacks for receiving data from the device + * and should not be called from driver code directly + * @{ + * + */ +void goodix_receive_done (FpDevice *dev, + guint8 *data, + guint16 length, + GError *error); + +void goodix_receive_success (FpDevice *dev, + guint8 *data, + guint16 length, + gpointer user_data, + GError *error); + +void goodix_receive_reset (FpDevice *dev, + guint8 *data, + guint16 length, + gpointer user_data, + GError *error); + +void goodix_receive_none (FpDevice *dev, + guint8 *data, + guint16 length, + gpointer user_data, + GError *error); + +void goodix_receive_none_tolerant (FpDevice *dev, + guint8 *data, + guint16 length, + gpointer user_data, + GError *error); + +void goodix_receive_default (FpDevice *dev, + guint8 *data, + guint16 length, + gpointer user_data, + GError *error); + +void goodix_receive_preset_psk_read (FpDevice *dev, + guint8 *data, + guint16 length, + gpointer user_data, + GError *error); + +void goodix_receive_preset_psk_write (FpDevice *dev, + guint8 *data, + guint16 length, + gpointer user_data, + GError *error); + +void goodix_receive_ack (FpDevice *dev, + guint8 *data, + guint16 length, + gpointer user_data, + GError *error); + +void goodix_receive_firmware_version (FpDevice *dev, + guint8 *data, + guint16 length, + gpointer user_data, + GError *error); + +void goodix_receive_protocol (FpDevice *dev, + guint8 *data, + guint32 length); + +void goodix_receive_pack (FpDevice *dev, + guint8 *data, + guint32 length); + +void goodix_receive_data_cb (FpiUsbTransfer *transfer, + FpDevice *dev, + gpointer user_data, + GError *error); + +void goodix_receive_timeout_cb (FpDevice *dev, + gpointer user_data); + +void goodix_receive_data (FpDevice *dev); + +/** @} */ + +/** + * @brief Start the read loop that lets us get data from the device asynchronously. Should be called upon device activation + * + * @param dev + */ +void goodix_start_read_loop (FpDevice *dev); +void goodix_stop_read_loop (FpDevice *dev); +// ---- GOODIX RECEIVE SECTION END ---- + +// ----------------------------------------------------------------------------- + +// ---- GOODIX SEND SECTION START ---- + +/** + * @brief Send raw data to the device over USB + * @note You should never need to call this directly from your driver! + * + * @param dev + * @param data data to be sent + * @param length length of the data + * @param free_func free function for the data or NULL + * @param error error output + * @return gboolean TRUE if successful, FALSE otherwise + */ +gboolean goodix_send_data (FpDevice *dev, + guint8 *data, + guint32 length, + GDestroyNotify free_func, + GError **error); + +/** + * @brief Send a single packet to the device + * @note You should never need to call this directly from your driver! + * + * @param dev + * @param flags + * @param payload + * @param length + * @param free_func + * @param error + * @return gboolean + */ +gboolean goodix_send_pack (FpDevice *dev, + guint8 flags, + guint8 *payload, + guint16 length, + GDestroyNotify free_func, + GError **error); + +/** + * @brief Low level function to send a protocol message to the device + * @note You should never need to call this directly from your driver! + * + * @param dev + * @param cmd command identifier + * @param payload command payload + * @param length length of payload + * @param free_func function to free the payload or NULL + * @param calc_checksum calculate and add the checksum to the data? + * @param timeout_ms timeout for recieving data back or 0 for none + * @param reply do we expect a reply from the device? + * @param callback + * @param user_data + */ +void goodix_send_protocol (FpDevice *dev, + guint8 cmd, + const guint8 *payload, + guint16 length, + GDestroyNotify free_func, + gboolean calc_checksum, + guint timeout_ms, + gboolean reply, + GoodixCmdCallback callback, + gpointer user_data); + +/** + * @brief Send nop to the device + * + * @param dev + * @param callback + * @param user_data + */ +void goodix_send_nop (FpDevice *dev, + GoodixNoneCallback callback, + gpointer user_data); + +/** + * @brief Tell the device we want an image from it. The response will be TLS encrypted so you probably don't + * want to call this from your driver, checkout goodix_tls_read_image() if you want an image from the device + * + * @param dev + * @param callback + * @param user_data + */ +void goodix_send_mcu_get_image (FpDevice *dev, + GoodixImageCallback callback, + gpointer user_data); + +/** + * @brief Tell the device we want to wait for the user to present their finger + * + * @param dev + * @param mode magic bytes + * @param free_func function to free the mode bytes or NULL + * @param callback called once the user has presented their finger or with an error. Note may be called at once if the mode bytes are wrong + * @param user_data + */ +void goodix_send_mcu_switch_to_fdt_down (FpDevice *dev, + const guint8 *mode, + guint16 length, + GDestroyNotify free_func, + GoodixDefaultCallback callback, + gpointer user_data); + + +/** + * @brief Tell the device we want to wait for the user to lift their finger off + * + * @param dev + * @param mode magic bytes + * @param free_func function to free the mode bytes or NULL + * @param callback called once the user has presented their finger or with an error. Note may be called at once if the mode bytes are wrong + * @param user_data + */ +void goodix_send_mcu_switch_to_fdt_up (FpDevice *dev, + const guint8 * mode, + guint16 length, + GDestroyNotify free_func, + GoodixDefaultCallback callback, + gpointer user_data); + +/** + * @brief Prep the device for fdt down and fdt up commands + * + * @param dev + * @param mode magic bytes + * @param free_func function to free mode bytes or NULL + * @param callback + * @param user_data + */ +void goodix_send_mcu_switch_to_fdt_mode (FpDevice *dev, + const guint8 * mode, + guint16 length, + GDestroyNotify free_func, + GoodixNoneCallback callback, + gpointer user_data); + +void goodix_send_nav_0 (FpDevice *dev, + GoodixDefaultCallback callback, + gpointer user_data); + +void goodix_send_mcu_switch_to_idle_mode (FpDevice *dev, + guint8 sleep_time, + GoodixNoneCallback callback, + gpointer user_data); + +void goodix_send_write_sensor_register (FpDevice *dev, + guint16 address, + guint16 value, + GoodixNoneCallback callback, + gpointer user_data); + +void goodix_send_read_sensor_register (FpDevice *dev, + guint16 address, + guint8 length, + GoodixDefaultCallback callback, + gpointer user_data); + +/** + * @brief Upload an MCU config to the device. Config may vary by device + * + * @param dev + * @param config config buffer + * @param length length of buffer + * @param free_func free function or NULL + * @param callback + * @param user_data + */ +void goodix_send_upload_config_mcu (FpDevice *dev, + guint8 *config, + guint16 length, + GDestroyNotify free_func, + GoodixSuccessCallback callback, + gpointer user_data); + +void goodix_send_set_powerdown_scan_frequency (FpDevice *dev, + guint16 powerdown_scan_frequency, + GoodixSuccessCallback callback, + gpointer user_data); + +/** + * @brief Turn the chip on + * + * @param dev + * @param enable + * @param callback + * @param user_data + */ +void goodix_send_enable_chip (FpDevice *dev, + gboolean enable, + GoodixNoneCallback callback, + gpointer user_data); + +/** + * @brief Send a reset command to the device + * + * @param dev + * @param reset_sensor + * @param sleep_time + * @param callback + * @param user_data + */ +void goodix_send_reset (FpDevice *dev, + gboolean reset_sensor, + guint8 sleep_time, + GoodixResetCallback callback, + gpointer user_data); + +/** + * @brief Ask the device what firmware version it is running. Response is null-terminated string + * + * @param dev + * @param callback + * @param user_data + */ +void goodix_send_query_firmware_version (FpDevice *dev, + GoodixFirmwareVersionCallback callback, + gpointer user_data); + +/** + * @brief Ask the device what the current mcu state is + * + * @param dev + * @param callback + * @param user_data + */ +void goodix_send_query_mcu_state (FpDevice *dev, + GoodixNoneCallback callback, + gpointer user_data); + +/** + * @brief Tell the device we want to start talking TLS + * @note You probably don't need to call this from your driver directly, checkout the goodix_tls_* functions + * + * @param dev + * @param callback + * @param user_data + */ +void goodix_send_request_tls_connection (FpDevice *dev, + GoodixDefaultCallback callback, + gpointer user_data); + +/** + * @brief Tell the device that we have successfully established TLS communication with it + * @note You probably don't need to call this from your driver directly, checkout the goodix_tls_* functions + * + * @param dev + * @param callback + * @param user_data + */ +void goodix_send_tls_successfully_established (FpDevice *dev, + GoodixNoneCallback callback, + gpointer user_data); + +void goodix_send_set_drv_state (FpDevice *dev, + GoodixNoneCallback cb, + gpointer ud); + +void goodix_send_mcu_get_pov_image (FpDevice *dev, + GoodixDefaultCallback cb, + gpointer ud); + +void goodix_send_set_pov_config (FpDevice *dev, + const guint8 *cfg, + guint16 len, + GDestroyNotify ff, + GoodixNoneCallback cb, + gpointer ud); + +/** + * @brief Set the device preset psk. May not work for all device firmware versions + * + * @param dev + * @param flags + * @param psk + * @param length + * @param free_func + * @param callback + * @param user_data + */ +void goodix_send_preset_psk_write (FpDevice *dev, + guint32 flags, + guint8 *psk, + guint16 length, + GDestroyNotify free_func, + GoodixSuccessCallback callback, + gpointer user_data); + +/** + * @brief Ask the device what preset psk it has + * + * @param dev + * @param flags flags for the command, possibly device specific? + * @param length + * @param callback + * @param user_data + */ +void goodix_send_preset_psk_read (FpDevice *dev, + guint32 flags, + guint16 length, + GoodixPresetPskReadCallback callback, + gpointer user_data); +/** + * @brief Request the OTP (One Time Password) from the device + * + * @param dev + * @param callback + * @param user_data + */ +void goodix_send_read_otp (FpDevice *dev, + GoodixDefaultCallback callback, + gpointer user_data); + +// ---- GOODIX SEND SECTION END ---- + +// ----------------------------------------------------------------------------- + +// ---- DEV SECTION START ---- + +/** + * @brief Claim the resources used for communcation with the device + * + * @param dev + * @param error + * @return gboolean + */ +gboolean goodix_dev_init (FpDevice *dev, + GError **error); + +/** + * @brief Cleanup the resources used to communicate with the device + * + * @param dev + * @param error + * @return gboolean + */ +gboolean goodix_dev_deinit (FpDevice *dev, + GError **error); + +/** + * @brief Reset the internal state of the communication with the device. Use this e.g. on device deactivation (where it may be reactivated again in future) + * + * @param dev + */ +void goodix_reset_state (FpDevice *dev); + +/** + * @brief Stale-activation guard generation. + * + * Counter bumped on (re)activation start and teardown; TLS completion + * callbacks drop on mismatch without touching hardware. + * + * @param dev + * @return current generation (get) or new generation after bump (bump) + */ +guint goodix_activation_gen_get (FpDevice *dev); +guint goodix_activation_gen_bump (FpDevice *dev); + +// ---- DEV SECTION END ---- + +// ----------------------------------------------------------------------------- + +// ---- TLS SECTION START ---- + +/** + * @brief Read a TLS packet from the device + * @note You probably won't ever need to call this directly from your driver + * + * @param dev + * @param callback + * @param user_data + */ +void goodix_read_tls (FpDevice *dev, + GoodixTlsCallback callback, + gpointer user_data); + +/** + * @brief Initialise TLS with the device. Performs handshaking and such for you + * + * @param dev + * @param callback + * @param user_data + */ +void goodix_tls_init (FpDevice *dev, + GoodixNoneCallback callback, + gpointer user_data); + +/** + * @brief Shutdown TLS communication with the device + * + * @param dev + * @param error + * @return gboolean TRUE if ok, FALSE otherwise + */ +gboolean goodix_shutdown_tls (FpDevice *dev, + GError **error); + +/** + * @brief Read a TLS encrypted image from the device and decrypt it + * + * @param dev + * @param callback Called when the image is decrypted + * @param user_data + */ +void goodix_tls_read_image (FpDevice *dev, + GoodixImageCallback callback, + gpointer user_data); + +// ---- TLS SECTION END ---- diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c new file mode 100644 index 000000000..4cbd7d79b --- /dev/null +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -0,0 +1,956 @@ +// Goodix TLS driver for libfprint - 27c6:5e0a (Realme Book / ChicagoH) +// Clean-room reverse engineering from passive USB captures of Windows driver traffic. + +// Copyright (C) 2026 The libfprint Goodix 5e0a contributors + +// 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 + +#include "drivers/goodixtls/goodix5xx.h" +#include "fp-device.h" +#include "fp-image-device.h" +#include "fp-image.h" +#include "fpi-assembling.h" +#include "fpi-context.h" +#include "fpi-image-device.h" +#include "fpi-image.h" +#include "fpi-ssm.h" +#include "glibconfig.h" +#include "gusb/gusb-device.h" +#include +#include +#include + +#define FP_COMPONENT "goodixtls5e0a" + +#include +#include + +#include "drivers_api.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wredundant-decls" +#include "nbis/include/lfs.h" +#pragma GCC diagnostic pop +#include "goodix.h" +#include "goodix_proto.h" +#include "goodix5e0a.h" + +struct _FpiDeviceGoodixTls5e0a +{ + FpiDeviceGoodixTls5xx parent; + + gboolean session_started; + FpiSsm *scan_ssm; + GSource *down_timeout; +}; + +G_DECLARE_FINAL_TYPE (FpiDeviceGoodixTls5e0a, fpi_device_goodixtls5e0a, FPI, + DEVICE_GOODIXTLS5E0A, FpiDeviceGoodixTls5xx); + +G_DEFINE_TYPE (FpiDeviceGoodixTls5e0a, fpi_device_goodixtls5e0a, + FPI_TYPE_DEVICE_GOODIXTLS5XX); + +// ---- ACTIVATE SECTION START ---- + +enum activate_states { + ACTIVATE_READ_AND_NOP, + ACTIVATE_RESET, + ACTIVATE_READ_CHIP_ID, + ACTIVATE_READ_OTP, + ACTIVATE_CHECK_FW_VER, + ACTIVATE_UPLOAD_CONFIG, + ACTIVATE_NUM_STATES, +}; + +/* No PSK reconciliation: activation goes CHECK_FW_VER -> UPLOAD_CONFIG -> TLS with the static host key (0xe4 slot reports factory bytes, 0xe0 writes rejected). */ + +static void +activate_run_state (FpiSsm *ssm, FpDevice *dev) +{ + switch (fpi_ssm_get_cur_state (ssm)) + { + case ACTIVATE_READ_AND_NOP: + goodix_start_read_loop (dev); + goodix_send_nop (dev, goodixtls5xx_check_none, ssm); + break; + + case ACTIVATE_RESET: + goodix_send_reset (dev, TRUE, 20, goodixtls5xx_check_reset, ssm); + break; + + case ACTIVATE_READ_CHIP_ID: + goodix_send_read_sensor_register (dev, 0x0000, 4, goodixtls5xx_check_none_cmd, ssm); + break; + + case ACTIVATE_READ_OTP: + goodix_send_read_otp (dev, goodixtls5xx_check_none_cmd, ssm); + break; + + case ACTIVATE_CHECK_FW_VER: + goodix_send_query_firmware_version (dev, goodixtls5xx_check_firmware_version, ssm); + break; + + case ACTIVATE_UPLOAD_CONFIG: + goodix_send_upload_config_mcu (dev, (guint8 *) goodix_5e0a_config, + sizeof (goodix_5e0a_config), NULL, + goodixtls5xx_check_config_upload, ssm); + break; + } +} + +static void +on_chip_enabled (FpDevice *dev, gpointer user_data, GError *error) +{ + if (error) + { + fp_err ("failed to enable chip: %s (code: %d)", error->message, error->code); + fpi_image_device_activate_complete (FP_IMAGE_DEVICE (dev), error); + return; + } + fp_dbg ("Chip enabled! Activation complete."); + fpi_image_device_activate_complete (FP_IMAGE_DEVICE (dev), NULL); +} + +static void +on_tls_activation_complete (FpDevice *dev, gpointer user_data, GError *error) +{ + /* Drop this completion if a deactivate bumped the generation while the TLS handshake was in flight. */ + if (GPOINTER_TO_UINT (user_data) != goodix_activation_gen_get (dev)) + { + fp_dbg ("dropping stale TLS activation completion"); + if (error) + g_error_free (error); + return; + } + + if (error) + { + fp_err ("failed during TLS activation: %s (code: %d)", error->message, error->code); + fpi_image_device_activate_complete (FP_IMAGE_DEVICE (dev), error); + return; + } + + fp_dbg ("TLS connection ready! Enabling chip..."); + goodix_send_enable_chip (dev, TRUE, on_chip_enabled, NULL); +} + +static void +activate_complete (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + G_DEBUG_HERE (); + if (!error) + { + /* Capture the activation generation for the staleness guard. */ + goodix_tls_init (dev, on_tls_activation_complete, + GUINT_TO_POINTER (goodix_activation_gen_get (dev))); + } + else + { + fp_err ("failed during activation: %s (code: %d)", error->message, error->code); + fpi_image_device_activate_complete (FP_IMAGE_DEVICE (dev), error); + } +} + +static void +dev_activate (FpImageDevice *img_dev) +{ + FpDevice *dev = FP_DEVICE (img_dev); + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + + /* Invalidate any in-flight activation from a previous session. */ + goodix_activation_gen_bump (dev); + + self->session_started = FALSE; + self->scan_ssm = NULL; + self->down_timeout = NULL; + + fpi_ssm_start (fpi_ssm_new (dev, activate_run_state, ACTIVATE_NUM_STATES), + activate_complete); +} + +// ---- ACTIVATE SECTION END ---- + +// ----------------------------------------------------------------------------- + +// ---- SCAN SECTION START (Windows-faithful steady-state port) ---- + +enum goodix5e0a_scan_states { + SCAN_5E0A_SESSION_AE, + SCAN_5E0A_SESSION_D6, + SCAN_5E0A_FDT_DOWN, + SCAN_5E0A_GET_IMAGE, + SCAN_5E0A_FDT_UP_1, + SCAN_5E0A_UP_AE, + SCAN_5E0A_FDT_UP_2, + SCAN_5E0A_NUM_STATES, +}; + +static void +send_cmd_noreply (FpDevice *dev, guint8 cmd, const guint8 *payload, guint16 len, + GoodixNoneCallback cb, gpointer user_data) +{ + GoodixCallbackInfo *cb_info = NULL; + GoodixCmdCallback callback = NULL; + + if (cb) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info->callback = G_CALLBACK (cb); + cb_info->user_data = user_data; + callback = goodix_receive_none; + } + + goodix_send_protocol (dev, cmd, payload, len, NULL, TRUE, GOODIX_TIMEOUT, + FALSE, callback, cb_info); +} + +static void +send_cmd_reply (FpDevice *dev, guint8 cmd, const guint8 *payload, guint16 len, + guint timeout_ms, GoodixDefaultCallback cb, gpointer user_data) +{ + GoodixCallbackInfo *cb_info = NULL; + GoodixCmdCallback callback = NULL; + + if (cb) + { + cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info->callback = G_CALLBACK (cb); + cb_info->user_data = user_data; + callback = goodix_receive_default; + } + + goodix_send_protocol (dev, cmd, payload, len, NULL, TRUE, timeout_ms, + TRUE, callback, cb_info); +} + +static void +goodix5e0a_step_cb (FpDevice *dev, gpointer user_data, GError *error) +{ + FpiSsm *ssm = user_data; + + if (error) + { + fp_dbg ("5e0a step cb tolerant error: %s", error->message); + g_error_free (error); + } + fpi_ssm_next_state (ssm); +} + +static void +goodix5e0a_on_d6_reply (FpDevice *dev, guint8 *data, guint16 len, + gpointer ssm, GError *err) +{ + if (err) + { + fp_warn ("5e0a session d6 reply error: %s", err->message); + g_error_free (err); + } + else + { + fp_dbg ("5e0a session d6 replied successfully (len=%u)", len); + } + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + self->session_started = TRUE; + fpi_ssm_next_state (ssm); +} + +static void goodix5e0a_on_fdt_down_reply (FpDevice *dev, + guint8 *data, + guint16 len, + gpointer ssm, + GError *err); + +static void +goodix5e0a_on_down_poll_timeout (FpDevice *dev, gpointer user_data) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + + self->down_timeout = NULL; + + FpiSsm *ssm = user_data; + if (self->scan_ssm != ssm) + return; + + send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, + goodix_5e0a_down_s12, sizeof (goodix_5e0a_down_s12), + 0, goodix5e0a_on_fdt_down_reply, ssm); +} + +static void +goodix5e0a_on_fdt_down_reply (FpDevice *dev, guint8 *data, guint16 len, + gpointer ssm, GError *err) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + + if (err) + { + if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + fpi_ssm_mark_failed (ssm, err); + return; + } + fpi_ssm_mark_failed (ssm, err); + return; + } + + guint8 status = (len > 0) ? data[0] : 0x00; + + GString *hex_str = g_string_new (""); + for (guint16 i = 0; i < len; i++) + g_string_append_printf (hex_str, "%02x ", data[i]); + fp_dbg ("5e0a D32 reply: status=0x%02x len=%u bytes=[%s]", status, len, hex_str->str); + g_string_free (hex_str, TRUE); + + guint32 channel_energy = 0; + if (len >= 4) + for (guint16 i = 4; i + 1 < len; i += 2) + channel_energy += (guint32) data[i] | ((guint32) data[i + 1] << 8); + + /* Gating rule: touch = channel-byte energy (data[2] != 0xff and channel_energy > 0), never byte0 */ + gboolean touch = (len >= 4 && data[2] != 0xff && channel_energy > 0); + + if (touch) + { + if (self->down_timeout) + { + g_source_destroy (self->down_timeout); + self->down_timeout = NULL; + } + fp_dbg ("5e0a D32 touch confirmed: mask=0x%02x energy=%u", + (data && len >= 3) ? data[2] : 0, channel_energy); + fpi_image_device_report_finger_status (FP_IMAGE_DEVICE (dev), TRUE); + fpi_ssm_next_state (ssm); + return; + } + + /* No touch (empty air or poor contact): pace re-sampling silently after 50ms */ + if (self->down_timeout) + { + g_source_destroy (self->down_timeout); + self->down_timeout = NULL; + } + self->down_timeout = fpi_device_add_timeout (dev, 50, goodix5e0a_on_down_poll_timeout, ssm, NULL); +} + +static FpImage * process_raw_frame (GoodixTls5xxPix * pix); +static guint goodix5e0a_count_minutiae (FpImage *img); + +static guint32 +goodix5e0a_decode_frame (GoodixTls5xxPix *out_row_major, const guint8 *data, guint16 len) +{ + guint8 packed[GOODIX_5E0A_ACT_BYTES] = {0}; + guint32 packed_len = 0; + + if (!data) + return 0; + + /* A canonical ChicagoH frame is 80 blocks of 132 bytes followed by a + * four-byte footer. Each block carries 96 packed pixel bytes and 36 zero + * padding bytes. The 80 active blocks are the natural rows of a 64x80 + * raster; keeping them in sequence avoids the destructive transpose used + * by the superseded decoder. */ + for (guint32 block = 0; block < GOODIX_5E0A_FRAME_BLOCKS; block++) + { + guint32 src = block * GOODIX_5E0A_BLOCK_BYTES; + if (src + GOODIX_5E0A_BLOCK_ACTIVE_BYTES > len) + break; + + memcpy (packed + packed_len, data + src, GOODIX_5E0A_BLOCK_ACTIVE_BYTES); + packed_len += GOODIX_5E0A_BLOCK_ACTIVE_BYTES; + } + + guint32 pixel_idx = 0; + for (guint32 i = 0; i + 6 <= packed_len && pixel_idx + 4 <= GOODIX_5E0A_FRAME_SIZE; i += 6) + { + const guint8 *c = packed + i; + out_row_major[pixel_idx++] = ((c[0] & 0x0f) << 8) | c[1]; + out_row_major[pixel_idx++] = (c[3] << 4) | (c[0] >> 4); + out_row_major[pixel_idx++] = ((c[5] & 0x0f) << 8) | c[2]; + out_row_major[pixel_idx++] = (c[4] << 4) | (c[5] >> 4); + } + + return pixel_idx; +} + +static void +goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, + gpointer ssm, GError *err) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + + if (err) + { + fpi_ssm_mark_failed (ssm, err); + return; + } + + fp_dbg ("5e0a scan_on_read_img: declen=%u", len); + + if (data && len >= 16) + { + fp_dbg ("5e0a raw first 16 bytes: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", + data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], + data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]); + } + + guint32 padding_nonzero = 0; + if (data) + { + for (guint32 block = 0; block < GOODIX_5E0A_FRAME_BLOCKS; block++) + { + guint32 pad = block * GOODIX_5E0A_BLOCK_BYTES + GOODIX_5E0A_BLOCK_ACTIVE_BYTES; + guint32 pad_end = MIN (pad + GOODIX_5E0A_BLOCK_BYTES - GOODIX_5E0A_BLOCK_ACTIVE_BYTES, + (guint32) len); + for (guint32 i = pad; i < pad_end; i++) + padding_nonzero += data[i] != 0; + } + } + + GoodixTls5xxPix *raw_frame = calloc (GOODIX_5E0A_FRAME_SIZE, sizeof (GoodixTls5xxPix)); + guint32 decoded_pixels = goodix5e0a_decode_frame (raw_frame, data, len); + + guint total_nonzero = 0; + guint16 raw_min = 65535, raw_max = 0; + for (guint32 i = 0; i < GOODIX_5E0A_FRAME_SIZE; i++) + { + if (raw_frame[i] > 0) + { + total_nonzero++; + if (raw_frame[i] < raw_min) + raw_min = raw_frame[i]; + if (raw_frame[i] > raw_max) + raw_max = raw_frame[i]; + } + } + fp_dbg ("5e0a wire layout: decoded_px=%u blocks=%u active_bytes=%u padding_nonzero=%u footer_bytes=%u", + decoded_pixels, MIN ((guint32) len / GOODIX_5E0A_BLOCK_BYTES, + (guint32) GOODIX_5E0A_FRAME_BLOCKS), + GOODIX_5E0A_BLOCK_ACTIVE_BYTES, padding_nonzero, + len >= GOODIX_5E0A_FRAME_WIRE_BYTES ? 4 : 0); + fp_dbg ("5e0a row-major frame: active_px=%u nonzero=%u min=%u max=%u geometry=%dx%d (WxH)", + decoded_pixels, total_nonzero, raw_min == 65535 ? 0 : raw_min, raw_max, + GOODIX_5E0A_WIDTH, GOODIX_5E0A_HEIGHT); + + FpImage *img = process_raw_frame (raw_frame); + free (raw_frame); + + if (img == NULL) + { + img = fp_image_new (GOODIX_5E0A_SCALED_WIDTH, GOODIX_5E0A_SCALED_HEIGHT); + img->flags = FPI_IMAGE_COLORS_INVERTED; + img->ppmm = 500.0 / 25.4; + } + + FpiDeviceAction action = fpi_device_get_current_action (dev); + if (action == FPI_DEVICE_ACTION_ENROLL) + { + guint minutiae_count = goodix5e0a_count_minutiae (img); + fp_dbg ("5e0a enrollment quality check: minutiae_count=%u (floor=%d)", + minutiae_count, GOODIX_5E0A_ENROLL_MIN_MINUTIAE); + if (minutiae_count < GOODIX_5E0A_ENROLL_MIN_MINUTIAE) + { + fp_dbg ("5e0a enrollment touch rejected: minutiae_count=%u < %d (press firmer)", + minutiae_count, GOODIX_5E0A_ENROLL_MIN_MINUTIAE); + g_object_unref (img); + fpi_image_device_retry_scan (FP_IMAGE_DEVICE (dev), FP_DEVICE_RETRY_TOO_SHORT); + fpi_ssm_next_state (ssm); + return; + } + } + + /* Verify passthrough: report capture immediately so auth finishes without waiting for finger-lift polls. */ + fpi_image_device_image_captured (FP_IMAGE_DEVICE (dev), img); + + if (action != FPI_DEVICE_ACTION_ENROLL) + { + self->scan_ssm = NULL; + fpi_ssm_mark_completed (ssm); + fpi_image_device_report_finger_status (FP_IMAGE_DEVICE (dev), FALSE); + } + else + { + fpi_ssm_next_state (ssm); + } +} + +static void +goodix5e0a_on_fdt_up_reply (FpDevice *dev, guint8 *data, guint16 len, + gpointer ssm, GError *err) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + + if (err) + { + fp_dbg ("5e0a D34 reply (tolerant): %s", err->message); + g_error_free (err); + } + else + { + fp_dbg ("5e0a D34 finger release reply: len=%u", len); + } + + /* Mark current scan SSM completed before notifying libfprint, + * so that when libfprint synchronously requests AWAIT_FINGER_ON, + * the concurrency guard does not block the new scan SSM. */ + self->scan_ssm = NULL; + fpi_ssm_next_state (ssm); + fpi_image_device_report_finger_status (FP_IMAGE_DEVICE (dev), FALSE); +} + +static void +goodix5e0a_scan_run_state (FpiSsm *ssm, FpDevice *dev) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + + switch (fpi_ssm_get_cur_state (ssm)) + { + case SCAN_5E0A_SESSION_AE: + send_cmd_noreply (dev, GOODIX_CMD_QUERY_MCU_STATE, + goodix_5e0a_query_ae, sizeof (goodix_5e0a_query_ae), + goodix5e0a_step_cb, ssm); + break; + + case SCAN_5E0A_SESSION_D6: + if (self->session_started) + { + fpi_ssm_jump_to_state (ssm, SCAN_5E0A_FDT_DOWN); + return; + } + send_cmd_reply (dev, GOODIX_CMD_SESSION_D6, + goodix_5e0a_session_d6, sizeof (goodix_5e0a_session_d6), + GOODIX_TIMEOUT, goodix5e0a_on_d6_reply, ssm); + break; + + case SCAN_5E0A_FDT_DOWN: + send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, + goodix_5e0a_down_s12, sizeof (goodix_5e0a_down_s12), + 0, goodix5e0a_on_fdt_down_reply, ssm); + break; + + case SCAN_5E0A_GET_IMAGE: + goodix_tls_read_image (dev, goodix5e0a_on_read_img, ssm); + break; + + case SCAN_5E0A_FDT_UP_1: + send_cmd_noreply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_UP, + goodix_5e0a_up_u01, sizeof (goodix_5e0a_up_u01), + goodix5e0a_step_cb, ssm); + break; + + case SCAN_5E0A_UP_AE: + send_cmd_noreply (dev, GOODIX_CMD_QUERY_MCU_STATE, + goodix_5e0a_query_ae, sizeof (goodix_5e0a_query_ae), + goodix5e0a_step_cb, ssm); + break; + + case SCAN_5E0A_FDT_UP_2: + send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_UP, + goodix_5e0a_up_u01, sizeof (goodix_5e0a_up_u01), + 5000, goodix5e0a_on_fdt_up_reply, ssm); + break; + } +} + +static void +goodix5e0a_scan_complete (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + + self->scan_ssm = NULL; + if (self->down_timeout) + { + g_source_destroy (self->down_timeout); + self->down_timeout = NULL; + } + + if (error) + { + fp_err ("5e0a failed to scan: %s (code: %d)", error->message, error->code); + fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error); + return; + } + fp_dbg ("5e0a finished scan stage"); +} + +static void +goodix5e0a_scan_start (FpDevice *dev) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + + if (self->scan_ssm != NULL) + { + fp_dbg ("5e0a scan SSM already active, ignoring start request"); + return; + } + + self->scan_ssm = fpi_ssm_new (dev, goodix5e0a_scan_run_state, SCAN_5E0A_NUM_STATES); + fpi_ssm_start (self->scan_ssm, goodix5e0a_scan_complete); +} + +static void +goodix5e0a_change_state (FpImageDevice *img_dev, FpiImageDeviceState state) +{ + if (state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON) + goodix5e0a_scan_start (FP_DEVICE (img_dev)); +} + +static void +goodix5e0a_deactivate (FpImageDevice *img_dev) +{ + FpDevice *dev = FP_DEVICE (img_dev); + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + + /* Orphan any in-flight TLS activation; its completion will drop. */ + goodix_activation_gen_bump (dev); + + self->session_started = FALSE; + if (self->down_timeout) + { + g_source_destroy (self->down_timeout); + self->down_timeout = NULL; + } + + goodix_reset_state (dev); + if (self->scan_ssm != NULL) + { + fpi_ssm_free (self->scan_ssm); + self->scan_ssm = NULL; + } + + GError *tls_err = NULL; + goodix_shutdown_tls (dev, &tls_err); + goodix_stop_read_loop (dev); + fpi_image_device_deactivate_complete (img_dev, tls_err); +} + +// ---- SCAN SECTION END ---- + +static void +fpi_device_goodixtls5e0a_init (FpiDeviceGoodixTls5e0a *self) +{ + self->session_started = FALSE; + self->scan_ssm = NULL; + self->down_timeout = NULL; +} + +static double +goodix5e0a_axis_correlation (const GoodixTls5xxPix *pix, + int width, + int height, + int dx, + int dy) +{ + double sum_a = 0.0, sum_b = 0.0; + guint count = 0; + + for (int y = 0; y + dy < height; y++) + for (int x = 0; x + dx < width; x++) + { + sum_a += pix[y * width + x]; + sum_b += pix[(y + dy) * width + x + dx]; + count++; + } + + if (count == 0) + return 0.0; + + double mean_a = sum_a / count; + double mean_b = sum_b / count; + double covariance = 0.0, variance_a = 0.0, variance_b = 0.0; + + for (int y = 0; y + dy < height; y++) + for (int x = 0; x + dx < width; x++) + { + double a = pix[y * width + x] - mean_a; + double b = pix[(y + dy) * width + x + dx] - mean_b; + covariance += a * b; + variance_a += a * a; + variance_b += b * b; + } + + double denominator = sqrt (variance_a * variance_b); + return denominator > 1e-6 ? covariance / denominator : 0.0; +} + +static FpImage * +process_raw_frame (GoodixTls5xxPix * pix) +{ + const int W = GOODIX_5E0A_WIDTH; + const int H = GOODIX_5E0A_HEIGHT; + const int dst_w = GOODIX_5E0A_SCALED_WIDTH; + const int dst_h = GOODIX_5E0A_SCALED_HEIGHT; + + guint16 min_v = 65535, max_v = 0; + guint active = 0; + + for (int r = 0; r < H; ++r) + { + for (int c = 0; c < W; ++c) + { + guint16 v = pix[r * W + c]; + if (v > 30) + { + active++; + if (v < min_v) + min_v = v; + if (v > max_v) + max_v = v; + } + } + } + + if (min_v == 65535) + min_v = 0; + guint16 range = (max_v > min_v) ? (max_v - min_v) : 1; + + double horizontal_corr = goodix5e0a_axis_correlation (pix, W, H, 1, 0); + double vertical_corr = goodix5e0a_axis_correlation (pix, W, H, 0, 1); + double horizontal_lag4_corr = goodix5e0a_axis_correlation (pix, W, H, 4, 0); + + GString *active_cols = g_string_new (""); + for (int c = 0; c < W; ++c) + { + guint32 c_sum = 0; + for (int r = 0; r < H; ++r) + c_sum += pix[r * W + c]; + if (c_sum > 0) + g_string_append_printf (active_cols, "%d ", c); + } + if (active_cols->len > 0) + fp_dbg ("5e0a active cols: %s", active_cols->str); + else + fp_dbg ("5e0a active cols: NONE (all 0)"); + g_string_free (active_cols, TRUE); + + fp_dbg ("5e0a frame stats: active=%u, min_v=%u, max_v=%u, range=%u, h_corr=%.3f, v_corr=%.3f, h_lag4_corr=%.3f (native %dx%d WxH)", + active, min_v, max_v, range, + horizontal_corr, vertical_corr, horizontal_lag4_corr, W, H); + + if (active < 64 || range < 8) + return NULL; + + /* Remove the slowly varying pressure/offset field before global scaling. + * A 3x3 local mean is the smallest window that removes this field without + * averaging across a full ridge period. */ + float residual[GOODIX_5E0A_FRAME_SIZE]; + float residual_min = G_MAXFLOAT; + float residual_max = -G_MAXFLOAT; + for (int y = 0; y < H; y++) + { + for (int x = 0; x < W; x++) + { + guint32 local_sum = 0; + guint local_count = 0; + for (int yy = MAX (0, y - 1); yy <= MIN (H - 1, y + 1); yy++) + for (int xx = MAX (0, x - 1); xx <= MIN (W - 1, x + 1); xx++) + { + local_sum += pix[yy * W + xx]; + local_count++; + } + + float value = pix[y * W + x] - (float) local_sum / local_count; + residual[y * W + x] = value; + residual_min = MIN (residual_min, value); + residual_max = MAX (residual_max, value); + } + } + + float residual_range = residual_max - residual_min; + fp_dbg ("5e0a local contrast: min=%.2f max=%.2f range=%.2f window=3x3 gain=%.2f", + residual_min, residual_max, residual_range, GOODIX_5E0A_CONTRAST_GAIN); + if (residual_range < 1.0f) + return NULL; + + guint8 normalized[GOODIX_5E0A_FRAME_SIZE]; + for (guint i = 0; i < GOODIX_5E0A_FRAME_SIZE; i++) + { + int value = (int) roundf (128.0f + residual[i] * GOODIX_5E0A_CONTRAST_GAIN); + normalized[i] = (guint8) CLAMP (value, 0, 255); + } + + /* Create the scaled 128x160 image directly via bilinear upscaling. + * Use FPI_IMAGE_COLORS_INVERTED for capacitive ridges (high ADC = black). + * Omit FPI_IMAGE_PARTIAL so remove_perimeter_pts=0 retains edge minutiae. */ + FpImage *scaled = fp_image_new (dst_w, dst_h); + scaled->flags = FPI_IMAGE_COLORS_INVERTED; + scaled->ppmm = 500.0 / 25.4; + + for (int y = 0; y < dst_h; y++) + { + float src_y = (y + 0.5f) * 0.5f - 0.5f; + if (src_y < 0.0f) + src_y = 0.0f; + int y0 = (int) src_y; + int y1 = (y0 + 1 < H) ? y0 + 1 : y0; + float y_frac = src_y - (float) y0; + + for (int x = 0; x < dst_w; x++) + { + float src_x = (x + 0.5f) * 0.5f - 0.5f; + if (src_x < 0.0f) + src_x = 0.0f; + int x0 = (int) src_x; + int x1 = (x0 + 1 < W) ? x0 + 1 : x0; + float x_frac = src_x - (float) x0; + + float top = (float) normalized[y0 * W + x0] * (1.0f - x_frac) + (float) normalized[y0 * W + x1] * x_frac; + float bot = (float) normalized[y1 * W + x0] * (1.0f - x_frac) + (float) normalized[y1 * W + x1] * x_frac; + float val = top * (1.0f - y_frac) + bot * y_frac; + int norm = (int) roundf (val); + scaled->data[y * dst_w + x] = (guint8) CLAMP (norm, 0, 255); + } + } + + fp_dbg ("5e0a scaled image: %dx%d (WxH) flags=0x%02x active=%u range=%u ppmm=%.3f", + scaled->width, scaled->height, scaled->flags, active, range, scaled->ppmm); + return scaled; +} + +static guint +goodix5e0a_count_minutiae (FpImage *img) +{ + if (!img || !img->data) + return 0; + + int w = img->width; + int h = img->height; + unsigned char *buf = g_memdup2 (img->data, w * h); + + if (img->flags & FPI_IMAGE_COLORS_INVERTED) + for (int i = 0; i < w * h; i++) + buf[i] = 255 - buf[i]; + + LFSPARMS parms = g_lfsparms_V2; + parms.remove_perimeter_pts = 0; + double ppmm = img->ppmm > 0 ? img->ppmm : (500.0 / 25.4); + + MINUTIAE *minutiae = NULL; + int *qmap = NULL, *dmap = NULL, *lcmap = NULL, *lfmap = NULL, *hcmap = NULL; + int mw, mh, bw, bh, bd; + unsigned char *bdata = NULL; + + int ret = get_minutiae (&minutiae, &qmap, &dmap, &lcmap, &lfmap, &hcmap, + &mw, &mh, &bdata, &bw, &bh, &bd, + buf, w, h, 8, ppmm, &parms); + guint count = (ret == 0 && minutiae) ? minutiae->num : 0; + + g_free (buf); + if (minutiae) + free_minutiae (minutiae); + if (qmap) + g_free (qmap); + if (dmap) + g_free (dmap); + if (lcmap) + g_free (lcmap); + if (lfmap) + g_free (lfmap); + if (hcmap) + g_free (hcmap); + if (bdata) + g_free (bdata); + + return count; +} + +void +goodix5e0a_suspend (FpDevice *dev) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + FpiDeviceAction action = fpi_device_get_current_action (dev); + + fp_dbg ("5e0a suspend requested during action: %d", action); + + /* Orphan any in-flight TLS handshake/activation; its completion will drop. */ + goodix_activation_gen_bump (dev); + + self->session_started = FALSE; + if (self->down_timeout) + { + g_source_destroy (self->down_timeout); + self->down_timeout = NULL; + } + + /* Reset in-flight protocol commands and timeout */ + goodix_reset_state (dev); + + /* Free in-flight scan state machine */ + if (self->scan_ssm != NULL) + { + fpi_ssm_free (self->scan_ssm); + self->scan_ssm = NULL; + } + + /* Terminate background read loop and cancel transfers */ + goodix_stop_read_loop (dev); + + /* Tear down TLS context */ + goodix_shutdown_tls (dev, NULL); + + /* Complete suspend with NOT_SUPPORTED to trigger clean core deactivation + * of the interactive task, releasing PAM claims before sleep. */ + fpi_device_suspend_complete (dev, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); +} + +void +goodix5e0a_resume (FpDevice *dev) +{ + fp_dbg ("5e0a resume requested"); + + /* Device state was cleaned up during suspend; complete resume immediately. + * Subsequent user claims will trigger clean open/activate and hardware re-priming. */ + fpi_device_resume_complete (dev, NULL); +} + +static void +fpi_device_goodixtls5e0a_class_init (FpiDeviceGoodixTls5e0aClass * class) +{ + FpiDeviceGoodixTlsClass * gx_class = FPI_DEVICE_GOODIXTLS_CLASS (class); + FpDeviceClass * dev_class = FP_DEVICE_CLASS (class); + FpImageDeviceClass * img_dev_class = FP_IMAGE_DEVICE_CLASS (class); + FpiDeviceGoodixTls5xxClass * xx_cls = FPI_DEVICE_GOODIXTLS5XX_CLASS (class); + + xx_cls->process_raw_frame = process_raw_frame; + xx_cls->scan_height = GOODIX_5E0A_HEIGHT; + xx_cls->scan_width = GOODIX_5E0A_WIDTH; + xx_cls->psk = goodix_5e0a_psk; + xx_cls->psk_flags = GOODIX_5E0A_PSK_FLAGS; + xx_cls->psk_len = sizeof (goodix_5e0a_psk); + xx_cls->firmware_version = GOODIX_5E0A_FIRMWARE_VERSION; + xx_cls->reset_number = GOODIX_5E0A_RESET_NUMBER; + xx_cls->has_calibration = FALSE; + + gx_class->interface = GOODIX_5E0A_INTERFACE; + gx_class->ep_in = GOODIX_5E0A_EP_IN; + gx_class->ep_out = GOODIX_5E0A_EP_OUT; + + dev_class->id = "goodixtls5e0a"; + dev_class->full_name = "Goodix TLS Fingerprint Sensor 5e0a"; + dev_class->type = FP_DEVICE_TYPE_USB; + dev_class->id_table = goodix_5e0a_id_table; + dev_class->nr_enroll_stages = 12; + dev_class->scan_type = FP_SCAN_TYPE_PRESS; + dev_class->temp_hot_seconds = -1; // Disable thermal watchdog + dev_class->suspend = goodix5e0a_suspend; + dev_class->resume = goodix5e0a_resume; + + img_dev_class->activate = dev_activate; + img_dev_class->change_state = goodix5e0a_change_state; + img_dev_class->deactivate = goodix5e0a_deactivate; + img_dev_class->bz3_threshold = 12; + img_dev_class->img_width = GOODIX_5E0A_SCALED_WIDTH; + img_dev_class->img_height = GOODIX_5E0A_SCALED_HEIGHT; + + fpi_device_class_auto_initialize_features (dev_class); +} diff --git a/libfprint/drivers/goodixtls/goodix5e0a.h b/libfprint/drivers/goodixtls/goodix5e0a.h new file mode 100644 index 000000000..4e564fc87 --- /dev/null +++ b/libfprint/drivers/goodixtls/goodix5e0a.h @@ -0,0 +1,130 @@ +// Goodix TLS driver for libfprint - 27c6:5e0a (Realme Book / ChicagoH) +// Clean-room reverse engineering from passive USB captures of Windows driver traffic. + +// Copyright (C) 2026 The libfprint Goodix 5e0a contributors + +// 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 + +#pragma once + +#define GOODIX_5E0A_INTERFACE (0) +#define GOODIX_5E0A_EP_IN (0x3 | FPI_USB_ENDPOINT_IN) +#define GOODIX_5E0A_EP_OUT (0x1 | FPI_USB_ENDPOINT_OUT) + +#define GOODIX_5E0A_FIRMWARE_VERSION ("GFUSB_GM168SEC_APP_10036") +#define GOODIX_5E0A_PSK_FLAGS (0xbb020001) +#define GOODIX_5E0A_RESET_NUMBER (2048) + +#define GOODIX_5E0A_WIDTH (64) +#define GOODIX_5E0A_HEIGHT (80) +#define GOODIX_5E0A_SCALED_WIDTH (128) +#define GOODIX_5E0A_SCALED_HEIGHT (160) +#define GOODIX_5E0A_SCAN_WIDTH (64) +#define GOODIX_5E0A_SCAN_HEIGHT (80) +#define GOODIX_5E0A_FRAME_SIZE (GOODIX_5E0A_WIDTH * GOODIX_5E0A_HEIGHT) +#define GOODIX_5E0A_FRAME_BLOCKS (80) +#define GOODIX_5E0A_BLOCK_BYTES (132) +#define GOODIX_5E0A_BLOCK_ACTIVE_BYTES (96) +#define GOODIX_5E0A_ACT_BYTES (GOODIX_5E0A_FRAME_BLOCKS * GOODIX_5E0A_BLOCK_ACTIVE_BYTES) /* 7680 */ +#define GOODIX_5E0A_FRAME_WIRE_BYTES (GOODIX_5E0A_FRAME_BLOCKS * GOODIX_5E0A_BLOCK_BYTES + 4) /* 10564 */ + +#define GOODIX_5E0A_CONTRAST_GAIN (1.0f) +#define GOODIX_5E0A_ENROLL_MIN_MINUTIAE (12) + + +// Sensor Analog Front-End (AFE) Gain/Exposure Register Configuration +#define GOODIX_5E0A_REG_GAIN_EXPOSURE (0x022c) +#define GOODIX_5E0A_REG_GAIN_EXPOSURE_VAL (0x0305) /* Little-endian 16-bit: \x05\x03 */ +#define GOODIX_5E0A_REG_GAIN_EXPOSURE_CALIB_VAL (0x030a) /* Little-endian 16-bit: \x0a\x03 */ +#define GOODIX_5E0A_REG_GAIN_EXPOSURE_RESET_VAL (0x020a) /* Little-endian 16-bit: \x0a\x02 */ + + +/* Static host TLS PSK for TLS_PSK_WITH_AES_128_CBC_SHA256, observed in passive + * USB captures of the Windows driver traffic. Activation uses it directly: + * the 0xe4-readable slot reports factory bytes (not the TLS key) and 0xe0 + * writes are rejected, so there is no on-device provisioning. Per-unit scope + * of this key is unconfirmed; see PR description. */ +static const guint8 goodix_5e0a_psk[] = { + 0xd8, 0x53, 0xad, 0x19, 0x41, 0xb2, 0xdc, 0x53, + 0x50, 0xc7, 0x66, 0xcd, 0x72, 0x6e, 0xf7, 0xa5, + 0xdf, 0x7d, 0x5f, 0xa3, 0x90, 0x53, 0xbf, 0xac, + 0x26, 0x9c, 0xe7, 0x52, 0xd7, 0xa8, 0xb2, 0xab +}; + +// ChicagoH GF3658 DN3 Configuration (256 bytes, wbdi.dll offset 0x197c50, checksum 0x0e53) +static const guint8 goodix_5e0a_config[256] = { + 0xb0, 0x11, 0x60, 0x71, 0x2c, 0x9d, 0x2c, 0xc9, 0x1c, 0xe5, 0x18, 0xfd, 0x00, 0xfd, 0x00, 0xfd, + 0x03, 0xba, 0x00, 0x01, 0x80, 0xca, 0x00, 0x04, 0x00, 0x84, 0x00, 0x15, 0xb3, 0x86, 0x00, 0x00, + 0xc4, 0x88, 0x00, 0x00, 0xba, 0x8a, 0x00, 0x00, 0xb2, 0x8c, 0x00, 0x00, 0xaa, 0x8e, 0x00, 0x00, + 0xc1, 0x90, 0x00, 0xbb, 0xbb, 0x92, 0x00, 0xb1, 0xb1, 0x94, 0x00, 0x00, 0xa8, 0x96, 0x00, 0x00, + 0xb6, 0x98, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, + 0x00, 0xd6, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x01, 0x05, 0xd0, 0x00, 0x00, + 0x00, 0x70, 0x00, 0x00, 0x00, 0x72, 0x00, 0x78, 0x56, 0x74, 0x00, 0x34, 0x12, 0x20, 0x00, 0x10, + 0x40, 0x2a, 0x01, 0x02, 0x04, 0x22, 0x00, 0x01, 0x20, 0x24, 0x00, 0x32, 0x00, 0x80, 0x00, 0x01, + 0x00, 0x5c, 0x00, 0x80, 0x00, 0x56, 0x00, 0x24, 0x20, 0x58, 0x00, 0x03, 0x02, 0x32, 0x00, 0x0c, + 0x02, 0x66, 0x00, 0x03, 0x00, 0x7c, 0x00, 0x00, 0x58, 0x82, 0x00, 0x80, 0x15, 0x2a, 0x01, 0x82, + 0x03, 0x22, 0x00, 0x01, 0x20, 0x24, 0x00, 0x14, 0x00, 0x80, 0x00, 0x01, 0x00, 0x5c, 0x00, 0x00, + 0x01, 0x56, 0x00, 0x04, 0x20, 0x58, 0x00, 0x03, 0x02, 0x32, 0x00, 0x0c, 0x02, 0x66, 0x00, 0x03, + 0x00, 0x7c, 0x00, 0x00, 0x58, 0x82, 0x00, 0x80, 0x15, 0x2a, 0x01, 0x08, 0x00, 0x5c, 0x00, 0x80, + 0x00, 0x54, 0x00, 0x10, 0x01, 0x62, 0x00, 0x04, 0x03, 0x64, 0x00, 0x19, 0x00, 0x66, 0x00, 0x03, + 0x00, 0x7c, 0x00, 0x01, 0x58, 0x2a, 0x01, 0x08, 0x00, 0x5c, 0x00, 0x00, 0x01, 0x52, 0x00, 0x08, + 0x00, 0x54, 0x00, 0x00, 0x01, 0x66, 0x00, 0x03, 0x00, 0x7c, 0x00, 0x01, 0x58, 0x00, 0x53, 0x0e +}; + +/* Windows capture ground truth tables (APP_10036, goodix-win.pcapng) */ +#define GOODIX_CMD_SESSION_D6 (0xd6) + +/* Session initialization commands */ +static const guint8 goodix_5e0a_query_ae[3] = {0x00, 0x01, 0x00}; +static const guint8 goodix_5e0a_session_d6[2] = {0x00, 0x00}; + +/* Exact 10-byte image capture payload: 05 00 b0 00 b2 00 b0 00 b1 00 (37/37 identical in capture) */ +static const guint8 goodix_5e0a_img_payload[10] = { + 0x05, 0x00, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00 +}; + +/* Exact 35-byte steady-state DOWN table S12 (pkts 302/328/406/432/792): + * 1c 01 b0 00 b2 00 b0 00 b1 00 + slots [80 b7 80 ce 80 aa 80 be 80 b1 80 c2] + 00 00 00 00 + b0 00 b2 00 b0 00 b1 00 00 */ +static const guint8 goodix_5e0a_down_s12[35] = { + 0x1c, 0x01, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, + 0x80, 0xb7, 0x80, 0xce, 0x80, 0xaa, 0x80, 0xbe, 0x80, 0xb1, 0x80, 0xc2, + 0x00, 0x00, 0x00, 0x00, + 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, 0x00 +}; + +/* Exact 35-byte DOWN retry table (pkt 114, sent after 0x80 poor contact status): + * 1c 01 b0 00 b2 00 b0 00 b1 00 + slots [80 b6 80 ce 80 aa 80 be 80 b2 80 c2] + 00 00 00 00 + b0 00 b2 00 b0 00 b1 00 00 */ +static const guint8 goodix_5e0a_down_retry[35] = { + 0x1c, 0x01, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, + 0x80, 0xb6, 0x80, 0xce, 0x80, 0xaa, 0x80, 0xbe, 0x80, 0xb2, 0x80, 0xc2, + 0x00, 0x00, 0x00, 0x00, + 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, 0x00 +}; + +/* Exact 35-byte steady-state UP table U01 (pkts 42/50): + * 0e 01 b0 00 b2 00 b0 00 b1 00 + slots [80 94 80 c2 80 97 80 b1 80 a5 80 21] + 13 zeros */ +static const guint8 goodix_5e0a_up_u01[35] = { + 0x0e, 0x01, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, + 0x80, 0x94, 0x80, 0xc2, 0x80, 0x97, 0x80, 0xb1, 0x80, 0xa5, 0x80, 0x21, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const FpIdEntry goodix_5e0a_id_table[] = { + {.vid = 0x27c6, .pid = 0x5e0a}, + {.vid = 0, .pid = 0, .driver_data = 0}, +}; + +void goodix5e0a_suspend (FpDevice *dev); +void goodix5e0a_resume (FpDevice *dev); diff --git a/libfprint/drivers/goodixtls/goodix5xx.c b/libfprint/drivers/goodixtls/goodix5xx.c new file mode 100644 index 000000000..79e322af6 --- /dev/null +++ b/libfprint/drivers/goodixtls/goodix5xx.c @@ -0,0 +1,642 @@ +// Goodix Tls driver for libfprint + +// Copyright (C) 2021 Alexander Meiler +// Copyright (C) 2021 Matthieu CHARETTE +// Copyright (C) 2021 Natasha England-Elbro + +// 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 +// +#include "fp-image-device.h" +#include "fpi-image-device.h" +#include "fpi-ssm.h" +#define FP_COMPONENT "goodixtls5xx" + +#include "drivers/goodixtls/goodix5xx.h" +#include "drivers_api.h" +#include "goodix.h" +#include +#include + + +typedef struct +{ + GoodixTls5xxPix * calibration_img; +} FpiDeviceGoodixTls5xxPrivate; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (FpiDeviceGoodixTls5xx, fpi_device_goodixtls5xx, FPI_TYPE_DEVICE_GOODIXTLS) + +enum CALIBRATION_STAGES { + CALIBRATION_STAGE_FDT_UP, + CALIBRATION_STAGE_NAV0, + CALIBRATION_STAGE_GET_IMG, + + CALIBRATION_STAGE_NUM, + +}; + +enum SCAN_STAGES { + SCAN_STAGE_QUERY_MCU, + SCAN_STAGE_SWITCH_TO_FDT_MODE, + SCAN_STAGE_CALIBRATE, + SCAN_STAGE_SWITCH_TO_FDT_DOWN_ARM, + SCAN_STAGE_SWITCH_TO_FDT_DOWN, + SCAN_STAGE_GET_IMG, + SCAN_STAGE_SWITCH_TO_FTD_UP, + SCAN_STAGE_SWITCH_TO_FTD_DONE, + + SCAN_STAGE_NUM, +}; + + +static void +send_switch_mode (FpDevice * dev, gpointer ssm, void (*mode_switch)(FpDevice *, + const guint8 *, + guint16, + GDestroyNotify, + GoodixDefaultCallback, + gpointer)) +{ + FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); + GoodixTls5xxMcuConfig cfg = cls->get_mcu_cfg (); + + mode_switch (dev, cfg.data, cfg.data_len, cfg.free_fn, goodixtls5xx_check_none_cmd, ssm); +} +static void +on_calibrate_scan (FpDevice * dev, guint8 * data, guint16 len, gpointer ssm, GError * err) +{ + if (err) + { + fpi_ssm_mark_failed (ssm, err); + return; + } + FpiDeviceGoodixTls5xx * self = FPI_DEVICE_GOODIXTLS5XX (dev); + FpiDeviceGoodixTls5xxPrivate * priv = fpi_device_goodixtls5xx_get_instance_private (self); + FpiDeviceGoodixTls5xxClass * cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (self); + if (!priv->calibration_img) + priv->calibration_img = calloc (cls->scan_height * cls->scan_width, sizeof (GoodixTls5xxPix)); + goodixtls5xx_decode_frame (priv->calibration_img, len, data); + + fpi_ssm_next_state (ssm); +} +static void +calibrate_run (FpiSsm * ssm, FpDevice * dev) +{ + switch (fpi_ssm_get_cur_state (ssm)) + { + case CALIBRATION_STAGE_FDT_UP: + { + FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); + if (cls->get_fdt_up_cfg) + { + GoodixTls5xxMcuConfig cfg = cls->get_fdt_up_cfg (); + goodix_send_mcu_switch_to_fdt_up (dev, cfg.data, cfg.data_len, cfg.free_fn, goodixtls5xx_check_none_cmd, ssm); + } + else + { + send_switch_mode (dev, ssm, goodix_send_mcu_switch_to_fdt_up); + } + } + break; + + case CALIBRATION_STAGE_NAV0: + goodix_send_nav_0 (dev, goodixtls5xx_check_none_cmd, ssm); + break; + + case CALIBRATION_STAGE_GET_IMG: + goodix_tls_read_image (dev, on_calibrate_scan, ssm); + } +} + +static void +do_calibration (FpDevice * dev, FpiSsm * parent) +{ + fpi_ssm_start_subsm (parent, fpi_ssm_new (dev, calibrate_run, CALIBRATION_STAGE_NUM)); +} + + +void +goodixtls5xx_check_none (FpDevice *dev, gpointer user_data, GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (user_data, error); + return; + } + + fpi_ssm_next_state (user_data); +} + +void +goodixtls5xx_check_none_cmd (FpDevice *dev, guint8 *data, guint16 len, + gpointer ssm, GError *err) +{ + if (err) + { + fpi_ssm_mark_failed (ssm, err); + return; + } + fpi_ssm_next_state (ssm); +} + +void +goodixtls5xx_check_firmware_version (FpDevice *dev, gchar *firmware, + gpointer user_data, GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (user_data, error); + return; + } + + fp_dbg ("Device firmware: \"%s\"", firmware); + FpiDeviceGoodixTls5xxClass * cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (FPI_DEVICE_GOODIXTLS5XX (dev)); + + if (strcmp (firmware, cls->firmware_version)) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid device firmware: \"%s\"", firmware); + fpi_ssm_mark_failed (user_data, error); + return; + } + + fpi_ssm_next_state (user_data); +} + + +void +goodixtls5xx_check_preset_psk_read (FpDevice *dev, gboolean success, + guint32 flags, guint8 *psk, guint16 length, + gpointer user_data, GError *error) +{ + g_autofree gchar *psk_str = data_to_str (psk, length); + + if (error) + { + fpi_ssm_mark_failed (user_data, error); + return; + } + + if (!success) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to read PSK from device"); + fpi_ssm_mark_failed (user_data, error); + return; + } + + fp_dbg ("Device PSK: 0x%s", psk_str); + fp_dbg ("Device PSK flags: 0x%08x", flags); + + FpiDeviceGoodixTls5xxClass * cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); + + if (flags != cls->psk_flags) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid device PSK flags: 0x%08x", flags); + fpi_ssm_mark_failed (user_data, error); + return; + } + + if (length != cls->psk_len) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid device PSK: 0x%s", psk_str); + fpi_ssm_mark_failed (user_data, error); + return; + } + + if (memcmp (psk, cls->psk, cls->psk_len)) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid device PSK: 0x%s", psk_str); + fpi_ssm_mark_failed (user_data, error); + return; + } + + fpi_ssm_next_state (user_data); +} + +void +goodixtls5xx_check_idle (FpDevice *dev, gpointer user_data, GError *err) +{ + + if (err) + { + fpi_ssm_mark_failed (user_data, err); + return; + } + fpi_ssm_next_state (user_data); +} +void +goodixtls5xx_check_config_upload (FpDevice *dev, gboolean success, + gpointer user_data, GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (user_data, error); + } + else if (!success) + { + fpi_ssm_mark_failed (user_data, + g_error_new (FP_DEVICE_ERROR, FP_DEVICE_ERROR_PROTO, + "failed to upload mcu config")); + } + else + { + fpi_ssm_next_state (user_data); + } +} +void +goodixtls5xx_check_reset (FpDevice *dev, gboolean success, guint16 number, + gpointer user_data, GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (user_data, error); + return; + } + + if (!success) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to reset device"); + fpi_ssm_mark_failed (user_data, error); + return; + } + + fp_dbg ("Device reset number: %d", number); + + FpiDeviceGoodixTls5xxClass * cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); + if (number != cls->reset_number) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid device reset number: %d", number); + fpi_ssm_mark_failed (user_data, error); + return; + } + + fpi_ssm_next_state (user_data); +} + +void +goodixtls5xx_check_powerdown_scan_freq (FpDevice *dev, gboolean success, + gpointer user_data, GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (user_data, error); + } + else if (!success) + { + fpi_ssm_mark_failed (user_data, + g_error_new (FP_DEVICE_ERROR, FP_DEVICE_ERROR_PROTO, + "failed to set powerdown freq")); + } + else + { + fpi_ssm_next_state (user_data); + } +} + +void +goodixtls5xx_squash_frame_linear (GoodixTls5xxPix *frame, guint8 *squashed, guint16 frame_size) +{ + GoodixTls5xxPix min = 0xffff; + GoodixTls5xxPix max = 0; + + for (int i = 0; i != frame_size; ++i) + { + const GoodixTls5xxPix pix = frame[i]; + if (pix < min) + min = pix; + if (pix > max) + max = pix; + } + + for (int i = 0; i != frame_size; ++i) + { + const GoodixTls5xxPix pix = frame[i]; + if (pix - min == 0 || max - min == 0) + squashed[i] = 0; + else + squashed[i] = (pix - min) * 0xff / (max - min); + } +} +static void +linear_subtract_inplace (GoodixTls5xxPix * src, GoodixTls5xxPix * by, guint16 len) +{ + for (guint16 n = 0; n != len; ++n) + src[n] = (src[n] > by[n]) ? (src[n] - by[n]) : 0; +} + +static void +scan_on_read_img (FpDevice *dev, guint8 *data, guint16 len, + gpointer ssm, GError *err) +{ + if (err) + { + fpi_ssm_mark_failed (ssm, err); + return; + } + + FpImageDevice * img_dev = FP_IMAGE_DEVICE (dev); + + FpiDeviceGoodixTls5xx * self = FPI_DEVICE_GOODIXTLS5XX (dev); + FpiDeviceGoodixTls5xxPrivate * priv = fpi_device_goodixtls5xx_get_instance_private (self); + FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); + + GoodixTls5xxPix * raw_frame = calloc (cls->scan_width * cls->scan_height, sizeof (GoodixTls5xxPix)); + goodixtls5xx_decode_frame (raw_frame, len, data); + if (priv->calibration_img) + linear_subtract_inplace (raw_frame, priv->calibration_img, cls->scan_width * cls->scan_height); + + FpImage * img = NULL; + if (cls->process_raw_frame) + { + img = cls->process_raw_frame (raw_frame); + } + else if (cls->process_frame) + { + guint8 * squashed = calloc (cls->scan_height * cls->scan_width, 1); + goodixtls5xx_squash_frame_linear (raw_frame, squashed, cls->scan_height * cls->scan_width); + img = cls->process_frame (squashed); + free (squashed); + } + free (raw_frame); + + fpi_image_device_image_captured (img_dev, img); + + fpi_ssm_next_state (ssm); +} + +static void +scan_get_img (FpDevice * dev, FpiSsm * ssm) +{ + goodix_tls_read_image (dev, scan_on_read_img, ssm); +} + +static void +scan_run_state (FpiSsm * ssm, FpDevice * dev) +{ + FpImageDevice *img_dev = FP_IMAGE_DEVICE (dev); + FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); + + switch (fpi_ssm_get_cur_state (ssm)) + { + case SCAN_STAGE_QUERY_MCU: + goodix_send_query_mcu_state (dev, goodixtls5xx_check_none, ssm); + break; + + case SCAN_STAGE_SWITCH_TO_FDT_MODE: + { + GoodixTls5xxMcuConfig cfg = cls->get_mcu_cfg (); + goodix_send_mcu_switch_to_fdt_mode (dev, cfg.data, cfg.data_len, cfg.free_fn, goodixtls5xx_check_none, ssm); + } + break; + + case SCAN_STAGE_CALIBRATE: + { + if (cls->has_calibration) + do_calibration (dev, ssm); + else + fpi_ssm_next_state (ssm); + } + break; + + case SCAN_STAGE_SWITCH_TO_FDT_DOWN_ARM: + fpi_ssm_next_state (ssm); + break; + + case SCAN_STAGE_SWITCH_TO_FDT_DOWN: + { + if (cls->get_fdt_down_cfg) + { + GoodixTls5xxMcuConfig cfg = cls->get_fdt_down_cfg (); + goodix_send_mcu_switch_to_fdt_down (dev, cfg.data, cfg.data_len, cfg.free_fn, goodixtls5xx_check_none_cmd, ssm); + } + else + { + send_switch_mode (dev, ssm, goodix_send_mcu_switch_to_fdt_down); + } + } + break; + + case SCAN_STAGE_GET_IMG: + fpi_image_device_report_finger_status (img_dev, TRUE); + scan_get_img (dev, ssm); + break; + + case SCAN_STAGE_SWITCH_TO_FTD_UP: + { + if (cls->get_mcu_cfg && cls->get_fdt_down_cfg) + { + GoodixTls5xxMcuConfig mode = cls->get_mcu_cfg (); + GoodixTls5xxMcuConfig down = cls->get_fdt_down_cfg (); + if (mode.data && down.data && mode.data_len == 27 && down.data_len >= 27) + { + guint8 post[27]; + memcpy (post, mode.data, sizeof (post)); + memcpy (post + 10, down.data + 10, 16); + post[26] = 0x00; + goodix_send_mcu_switch_to_fdt_mode (dev, post, sizeof (post), NULL, goodixtls5xx_check_none, ssm); + break; + } + } + if (cls->get_fdt_up_cfg) + { + GoodixTls5xxMcuConfig cfg = cls->get_fdt_up_cfg (); + goodix_send_mcu_switch_to_fdt_up (dev, cfg.data, cfg.data_len, cfg.free_fn, goodixtls5xx_check_none_cmd, ssm); + } + else + { + send_switch_mode (dev, ssm, goodix_send_mcu_switch_to_fdt_up); + } + } + break; + + case SCAN_STAGE_SWITCH_TO_FTD_DONE: + fpi_image_device_report_finger_status (img_dev, FALSE); + fpi_ssm_next_state (ssm); + break; + } +} + +static void +scan_complete (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + if (error) + { + fp_err ("failed to scan: %s (code: %d)", error->message, error->code); + fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error); + return; + } + fp_dbg ("finished scan"); +} + + +void +goodixtls5xx_scan_start (FpiDeviceGoodixTls5xx * dev) +{ + fpi_ssm_start (fpi_ssm_new (FP_DEVICE (dev), scan_run_state, SCAN_STAGE_NUM), scan_complete); +} + +void +goodixtls5xx_decode_frame (GoodixTls5xxPix * frame, guint32 frame_size, const guint8 *raw_frame) +{ + GoodixTls5xxPix *pix = frame; + guint32 start = 0; + guint32 end = (frame_size >= 4) ? (frame_size - 4) : frame_size; + + if (frame_size >= 13 && (frame_size - 13) % 6 == 0) + { + start = 8; + end = frame_size - 5; + } + + for (guint32 i = start; i + 6 <= end; i += 6) + { + const guint8 *chunk = raw_frame + i; + *pix++ = ((chunk[0] & 0xf) << 8) + chunk[1]; + *pix++ = (chunk[3] << 4) + (chunk[0] >> 4); + *pix++ = ((chunk[5] & 0xf) << 8) + chunk[2]; + *pix++ = (chunk[4] << 4) + (chunk[5] >> 4); + } +} + +static void +dev_change_state (FpImageDevice * img_dev, FpiImageDeviceState state) +{ + if (state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON) + goodixtls5xx_scan_start (FPI_DEVICE_GOODIXTLS5XX (img_dev)); +} +static void +dev_deinit (FpImageDevice * img_dev) +{ + FpDevice *dev = FP_DEVICE (img_dev); + GError *error = NULL; + + if (goodix_dev_deinit (dev, &error)) + { + fpi_image_device_close_complete (img_dev, error); + return; + } + + fpi_image_device_close_complete (img_dev, NULL); +} +static void +dev_init (FpImageDevice *img_dev) +{ + FpDevice *dev = FP_DEVICE (img_dev); + GError *error = NULL; + + if (goodix_dev_init (dev, &error)) + { + fpi_image_device_open_complete (img_dev, error); + return; + } + + fpi_image_device_open_complete (img_dev, NULL); +} + +static void +dev_deactivate (FpImageDevice *img_dev) +{ + FpDevice *dev = FP_DEVICE (img_dev); + + /* Orphan any in-flight TLS activation; its completion will drop. */ + goodix_activation_gen_bump (dev); + + goodix_reset_state (dev); + GError *error = NULL; + + goodix_shutdown_tls (dev, &error); + + FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); + goodixtls5xx_cleanup (FPI_DEVICE_GOODIXTLS5XX (dev)); + + if (cls->reset_state) + cls->reset_state (dev); + goodix_stop_read_loop (dev); + fpi_image_device_deactivate_complete (img_dev, error); +} + +static void +tls_activation_complete (FpDevice *dev, gpointer user_data, + GError *error) +{ + /* Drop orphaned completions without touching hardware. */ + if (GPOINTER_TO_UINT (user_data) != goodix_activation_gen_get (dev)) + { + fp_dbg ("dropping stale TLS activation completion"); + if (error) + g_error_free (error); + return; + } + + if (error) + { + fp_err ("failed to complete tls activation: %s", error->message); + FpImageDevice *image_dev = FP_IMAGE_DEVICE (dev); + fpi_image_device_activate_complete (image_dev, error); + return; + } + FpImageDevice *image_dev = FP_IMAGE_DEVICE (dev); + + fpi_image_device_activate_complete (image_dev, error); +} + +void +goodixtls5xx_init_tls (FpDevice * dev) +{ + /* Capture the activation generation for the staleness guard. */ + goodix_tls_init (dev, tls_activation_complete, + GUINT_TO_POINTER (goodix_activation_gen_get (dev))); +} + +void +fpi_device_goodixtls5xx_class_init (FpiDeviceGoodixTls5xxClass * self) +{ + self->get_mcu_cfg = NULL; + self->get_fdt_down_cfg = NULL; + self->get_fdt_up_cfg = NULL; + self->process_frame = NULL; + self->scan_height = 0; + self->scan_width = 0; + self->reset_state = NULL; + + FpImageDeviceClass *img_cls = FP_IMAGE_DEVICE_CLASS (self); + + img_cls->change_state = dev_change_state; + img_cls->deactivate = dev_deactivate; + img_cls->img_close = dev_deinit; + img_cls->img_open = dev_init; +} + +void +fpi_device_goodixtls5xx_init (FpiDeviceGoodixTls5xx * self) +{ + FpiDeviceGoodixTls5xxPrivate * priv = fpi_device_goodixtls5xx_get_instance_private (self); + + priv->calibration_img = NULL; +} + +void +goodixtls5xx_cleanup (FpiDeviceGoodixTls5xx * dev) +{ + FpiDeviceGoodixTls5xxPrivate * priv = fpi_device_goodixtls5xx_get_instance_private (dev); + + g_free (priv->calibration_img); + priv->calibration_img = NULL; +} \ No newline at end of file diff --git a/libfprint/drivers/goodixtls/goodix5xx.h b/libfprint/drivers/goodixtls/goodix5xx.h new file mode 100644 index 000000000..46b18e788 --- /dev/null +++ b/libfprint/drivers/goodixtls/goodix5xx.h @@ -0,0 +1,248 @@ +// Goodix Tls driver for libfprint + +// Copyright (C) 2021 Alexander Meiler +// Copyright (C) 2021 Matthieu CHARETTE +// Copyright (C) 2021 Natasha England-Elbro + +// 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 + +#pragma once + +#include "drivers_api.h" +#include "goodix.h" + +#define FPI_TYPE_DEVICE_GOODIXTLS5XX (fpi_device_goodixtls5xx_get_type ()) + +G_DECLARE_DERIVABLE_TYPE (FpiDeviceGoodixTls5xx, fpi_device_goodixtls5xx, FPI, DEVICE_GOODIXTLS5XX, FpiDeviceGoodixTls); + +/** + * @brief API for the common parts of communication between the goodixtls 5xx device + * @details This API is designed to make it easier to write and maintain + * drivers for the goodixtls 5xx (usb) devices. For an example out goodix511.c. + * + * @par The bare minimum needed is to provide get_mcu_cfg and process_frame to + * FpiDeviceGoodixTls5xxClass and activate to FpImageDeviceClass (activate + * varies from device to device) + * + * @par There are also quite a few helper functions in the goodixtls5xx_* + * namespace, the check functions expect a state machine as the user data and + * will advance it or mark it as failed as appropriate + * @struct FpiDeviceGoodixTls5xx + * + */ + +typedef guint16 GoodixTls5xxPix; + + +typedef struct +{ + guint16 data_len; + void (*free_fn)(void *); + const guint8 * data; +} GoodixTls5xxMcuConfig; + +typedef FpImage *(*GoodixTls5xxProcessFrameFn)(guint8 * frame); +typedef FpImage *(*GoodixTls5xxProcessRawFrameFn)(GoodixTls5xxPix * frame); +typedef GoodixTls5xxMcuConfig (*GoodixTls5xxGetMcuFn)(void); +typedef void (*GoodixTls5xxResetStateFn)(FpDevice *); + +struct _FpiDeviceGoodixTls5xxClass +{ + FpiDeviceGoodixTlsClass parent; + + GoodixTls5xxGetMcuFn get_mcu_cfg; ///< provide the mcu config before fdt commands + GoodixTls5xxGetMcuFn get_fdt_down_cfg; + GoodixTls5xxGetMcuFn get_fdt_up_cfg; + GoodixTls5xxProcessFrameFn process_frame; ///< process a frame after it is decoded (e.g. crop it) + GoodixTls5xxProcessRawFrameFn process_raw_frame; ///< process raw 12-bit ADC frame directly + GoodixTls5xxResetStateFn reset_state; ///< callback to reset the state, may be NULL + + guint16 scan_width; ///< width of the raw scanner image + guint16 scan_height; ///< height of the raw scanner image + + const char * firmware_version; ///< only needed if goodixtls5xx_check_firmware_version() is used + + /// only needed if goodixtls5xx_check_preset_psk_read() is used + int psk_flags; + guint16 psk_len; + const guint8 * psk; + + int reset_number; ///< only needed if goodixtls5xx_check_reset() is used + gboolean has_calibration; ///< TRUE if device requires calibration step (e.g. 511) +}; + +/** + * @brief Check the reply to a reset command + * + * @param dev + * @param success + * @param number + * @param user_data + * @param error + */ +void goodixtls5xx_check_reset (FpDevice *dev, + gboolean success, + guint16 number, + gpointer user_data, + GError *error); + +/** + * @brief Check the reply to a firmware version query matches configured firmware + * @note Requires firmware_version field to be set + * + * @param dev + * @param firmware + * @param user_data + * @param error + */ +void goodixtls5xx_check_firmware_version (FpDevice *dev, + gchar *firmware, + gpointer user_data, + GError *error); + + +/** + * @brief Check the reply to a preset psk query matched configured psk + * @note Requires psk_flags, psk_len, and psk fields to be set + * + * @param dev + * @param success + * @param flags + * @param psk + * @param length + * @param user_data + * @param error + */ +void goodixtls5xx_check_preset_psk_read (FpDevice *dev, + gboolean success, + guint32 flags, + guint8 *psk, + guint16 length, + gpointer user_data, + GError *error); + +/** + * @brief Check the reply to an idle command + * + * @param dev + * @param user_data + * @param err + */ +void goodixtls5xx_check_idle (FpDevice *dev, + gpointer user_data, + GError *err); + +/** + * @brief Check the reply to uploading an mcu config + * + * @param dev + * @param success + * @param user_data + * @param error + */ +void goodixtls5xx_check_config_upload (FpDevice *dev, + gboolean success, + gpointer user_data, + GError *error); + +/** + * @brief Check the reply to a powerdown_scan_freq command + * + * @param dev + * @param success + * @param user_data + * @param error + */ +void goodixtls5xx_check_powerdown_scan_freq (FpDevice *dev, + gboolean success, + gpointer user_data, + GError *error); + +/** + * @brief General check for GoodixNoneCallback replies + * + * @param dev + * @param user_data state machine to advance + * @param error + */ +void goodixtls5xx_check_none (FpDevice *dev, + gpointer user_data, + GError *error); + +/** + * @brief General callback for GoodixDefaultCallback replies + * + * @param dev + * @param data + * @param len + * @param ssm State machine to advance (userdata) + * @param err + */ +void goodixtls5xx_check_none_cmd (FpDevice *dev, + guint8 *data, + guint16 len, + gpointer ssm, + GError *err); + +/** + * @brief Start a scan + * @note This is called automatically for you unless you overwrote change_state in FpImageDeviceClass + * + * @param dev + */ +void goodixtls5xx_scan_start (FpiDeviceGoodixTls5xx * dev); + +/** + * @brief Decode a goodixtls frame + * @details Decodes the weird 4/6 byte packing: https://blog.th0m.as/misc/fingerprint-reversing/ + * @note Doesn't decrypt it + * + * @param frame + * @param frame_size + * @param raw_frame + */ +void goodixtls5xx_decode_frame (GoodixTls5xxPix * frame, + guint32 frame_size, + const guint8 *raw_frame); + + +/** + * @brief Initalise the TLS for the device + * @note You probably want to call this directly after device activation + * + * @param dev + */ +void goodixtls5xx_init_tls (FpDevice * dev); + +/** + * @brief Squashes the 2 byte pixels of a raw frame into the 1 byte pixels used + * by libfprint. + * @details Borrowed from the elan driver. We reduce frames to + * within the max and min. + * + * @param frame + * @param squashed + */ +void goodixtls5xx_squash_frame_linear (GoodixTls5xxPix *frame, + guint8 *squashed, + guint16 frame_size); + +/** + * @brief Cleans up the state after activation. If you replaced the deactivate callback + * then you will need to call this, otherwise don't worry its done for you + * + * @param dev device to cleanup the state for + */ +void goodixtls5xx_cleanup (FpiDeviceGoodixTls5xx * dev); \ No newline at end of file diff --git a/libfprint/drivers/goodixtls/goodix_proto.c b/libfprint/drivers/goodixtls/goodix_proto.c new file mode 100644 index 000000000..b8e6dfbfe --- /dev/null +++ b/libfprint/drivers/goodixtls/goodix_proto.c @@ -0,0 +1,141 @@ +// Goodix Tls driver for libfprint + +// Copyright (C) 2021 Alexander Meiler +// Copyright (C) 2021 Matthieu CHARETTE +// Copyright (C) 2021 Natasha England-Elbro + +// 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 + +#include +#include +#include + +#include "goodix_proto.h" + +guint8 +goodix_calc_checksum (guint8 *data, guint16 length) +{ + guint8 checksum = 0; + + for (guint16 i = 0; i < length; i++) + checksum += data[i]; + + return checksum; +} + +void +goodix_encode_pack (guint8 flags, guint8 *payload, guint16 payload_len, + gboolean pad_data, guint8 **data, guint32 *data_len) +{ + GoodixPack *pack; + + *data_len = sizeof (GoodixPack) + sizeof (guint8) + payload_len; + + if (pad_data && *data_len % GOODIX_EP_OUT_MAX_BUF_SIZE) + *data_len += + GOODIX_EP_OUT_MAX_BUF_SIZE - *data_len % GOODIX_EP_OUT_MAX_BUF_SIZE; + + *data = g_malloc0 (*data_len); + pack = (GoodixPack *) *data; + + pack->flags = flags; + pack->length = GUINT16_TO_LE (payload_len); + (*data)[sizeof (GoodixPack)] = goodix_calc_checksum (*data, sizeof (GoodixPack)); + + memcpy (*data + sizeof (GoodixPack) + sizeof (guint8), payload, payload_len); +} + +void +goodix_encode_protocol (guint8 cmd, const guint8 *payload, guint16 payload_len, + gboolean calc_checksum, gboolean pad_data, + guint8 **data, guint32 *data_len) +{ + GoodixProtocol *protocol; + + *data_len = sizeof (GoodixProtocol) + payload_len + sizeof (guint8); + + if (pad_data && *data_len % GOODIX_EP_OUT_MAX_BUF_SIZE) + *data_len += + GOODIX_EP_OUT_MAX_BUF_SIZE - *data_len % GOODIX_EP_OUT_MAX_BUF_SIZE; + + *data = g_malloc0 (*data_len); + protocol = (GoodixProtocol *) *data; + + protocol->cmd = cmd; + protocol->length = GUINT16_TO_LE (payload_len + sizeof (guint8)); + + memcpy (*data + sizeof (GoodixProtocol), payload, payload_len); + + if (calc_checksum) + (*data)[sizeof (GoodixProtocol) + payload_len] = + 0xaa - + goodix_calc_checksum (*data, sizeof (GoodixProtocol) + payload_len); + else + (*data)[sizeof (GoodixProtocol) + payload_len] = GOODIX_NULL_CHECKSUM; +} + +gboolean +goodix_decode_pack (guint8 *data, guint32 data_len, guint8 *flags, + guint8 **payload, guint16 *payload_len, + gboolean *valid_checksum) +{ + GoodixPack *pack = (GoodixPack *) data; + guint16 length; + + if (data_len < sizeof (GoodixPack) + sizeof (guint8)) + return FALSE; + + length = GUINT16_FROM_LE (pack->length); + + if (data_len < length + sizeof (GoodixPack) + sizeof (guint8)) + return FALSE; + + *flags = pack->flags; + *payload = g_memdup (data + sizeof (GoodixPack) + sizeof (guint8), length); + *payload_len = length; + *valid_checksum = goodix_calc_checksum (data, sizeof (GoodixPack)) == + data[sizeof (GoodixPack)]; + + return TRUE; +} + +gboolean +goodix_decode_protocol (guint8 *data, guint32 data_len, guint8 *cmd, + guint8 **payload, guint16 *payload_len, + gboolean *valid_checksum, + gboolean *valid_null_checksum) +{ + GoodixProtocol *protocol = (GoodixProtocol *) data; + guint16 length; + + if (data_len < sizeof (GoodixProtocol) + sizeof (guint8)) + return FALSE; + + length = GUINT16_FROM_LE (protocol->length) - sizeof (guint8); + + if (data_len < length + sizeof (GoodixProtocol) + sizeof (guint8)) + return FALSE; + + *cmd = protocol->cmd; + *payload = g_memdup (data + sizeof (GoodixProtocol), length); + *payload_len = length; + *valid_checksum = + 0xaa - goodix_calc_checksum (data, sizeof (GoodixProtocol) + length) == + data[sizeof (GoodixProtocol) + length]; + *valid_null_checksum = + GOODIX_NULL_CHECKSUM == data[sizeof (GoodixProtocol) + length]; + + return TRUE; +} diff --git a/libfprint/drivers/goodixtls/goodix_proto.h b/libfprint/drivers/goodixtls/goodix_proto.h new file mode 100644 index 000000000..8f55c8eb9 --- /dev/null +++ b/libfprint/drivers/goodixtls/goodix_proto.h @@ -0,0 +1,175 @@ +// Goodix Tls driver for libfprint + +// Copyright (C) 2021 Alexander Meiler +// Copyright (C) 2021 Matthieu CHARETTE +// Copyright (C) 2021 Natasha England-Elbro + +// 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 + +#pragma once + +#define GOODIX_EP_IN_MAX_BUF_SIZE (0x10000) +#define GOODIX_EP_OUT_MAX_BUF_SIZE (0x40) + +#define GOODIX_NULL_CHECKSUM (0x88) + +#define GOODIX_FLAGS_MSG_PROTOCOL (0xa0) +#define GOODIX_FLAGS_TLS (0xb0) +#define GOODIX_FLAGS_TLS_DATA (0xb2) + +#define GOODIX_CMD_NOP (0x00) +#define GOODIX_CMD_MCU_GET_IMAGE (0x20) +#define GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN (0x32) +#define GOODIX_CMD_MCU_SWITCH_TO_FDT_UP (0x34) +#define GOODIX_CMD_MCU_SWITCH_TO_FDT_MODE (0x36) +#define GOODIX_CMD_NAV_0 (0x50) +#define GOODIX_CMD_MCU_SWITCH_TO_IDLE_MODE (0x70) +#define GOODIX_CMD_WRITE_SENSOR_REGISTER (0x80) +#define GOODIX_CMD_READ_SENSOR_REGISTER (0x82) +#define GOODIX_CMD_UPLOAD_CONFIG_MCU (0x90) +#define GOODIX_CMD_SET_POWERDOWN_SCAN_FREQUENCY (0x94) +#define GOODIX_CMD_ENABLE_CHIP (0x96) +#define GOODIX_CMD_RESET (0xa2) +#define GOODIX_CMD_READ_OTP (0xa6) +#define GOODIX_CMD_FIRMWARE_VERSION (0xa8) +#define GOODIX_CMD_SET_POV_CONFIG (0xac) +#define GOODIX_CMD_QUERY_MCU_STATE (0xae) +#define GOODIX_CMD_ACK (0xb0) +#define GOODIX_CMD_SET_DRV_STATE (0xc4) +#define GOODIX_CMD_REQUEST_TLS_CONNECTION (0xd0) +#define GOODIX_CMD_MCU_GET_POV_IMAGE (0xd2) +#define GOODIX_CMD_TLS_SUCCESSFULLY_ESTABLISHED (0xd4) +#define GOODIX_CMD_PRESET_PSK_WRITE (0xe0) +#define GOODIX_CMD_PRESET_PSK_READ (0xe4) + +typedef struct __attribute__((__packed__)) _GoodixPack +{ + guint8 flags; + guint16 length; +} GoodixPack; + +typedef struct __attribute__((__packed__)) _GoodixProtocol +{ + guint8 cmd; + guint16 length; +} GoodixProtocol; + +typedef struct __attribute__((__packed__)) _GoodixAck +{ + guint8 cmd; + guint8 always_true : 1; + guint8 has_no_config : 1; + guint8 : 6; +} GoodixAck; + +typedef struct __attribute__((__packed__)) _GoodixNop +{ + guint32 unknown; +} GoodixNop; + +typedef struct __attribute__((__packed__)) _GoodixMcuSwitchToIdleMode +{ + guint8 sleep_time; + guint8 : 8; +} GoodixMcuSwitchToIdleMode; + +typedef struct __attribute__((__packed__)) _GoodixWriteSensorRegister +{ + guint8 multiples; + guint16 address; + guint16 value; +} GoodixWriteSensorRegister; + +typedef struct __attribute__((__packed__)) _GoodixReadSensorRegister +{ + guint8 multiples; + guint16 address; + guint8 length; + guint8 : 8; +} GoodixReadSensorRegister; + +typedef struct __attribute__((__packed__)) _GoodixSetPowerdownScanFrequency +{ + guint16 powerdown_scan_frequency; +} GoodixSetPowerdownScanFrequency; + +typedef struct __attribute__((__packed__)) _GoodixEnableChip +{ + guint8 enable; + guint8 : 8; +} GoodixEnableChip; + +typedef struct __attribute__((__packed__)) _GoodixReset +{ + guint8 reset_sensor : 1; + guint8 soft_reset_mcu : 1; + guint8 : 6; + guint8 sleep_time; +} GoodixReset; + +typedef struct __attribute__((__packed__)) _GoodixQueryMcuState +{ + guint8 unused_flags; +} GoodixQueryMcuState; + +typedef struct __attribute__((__packed__)) _GoodixPresetPsk +{ + guint32 flags; + guint32 length; +} GoodixPresetPsk; + +typedef struct __attribute__((__packed__)) _GoodixDefault +{ + guint8 unused_flags; + guint8 : 8; +} GoodixDefault; + +typedef struct __attribute__((__packed__)) _GoodixNone +{ + guint16 : 16; +} GoodixNone; + +guint8 goodix_calc_checksum (guint8 *data, + guint16 length); + +void goodix_encode_pack (guint8 flags, + guint8 *payload, + guint16 payload_len, + gboolean pad_data, + guint8 **data, + guint32 *data_len); + +void goodix_encode_protocol (guint8 cmd, + const guint8 *payload, + guint16 payload_len, + gboolean calc_checksum, + gboolean pad_data, + guint8 **data, + guint32 *data_len); + +gboolean goodix_decode_pack (guint8 *data, + guint32 data_len, + guint8 *flags, + guint8 **payload, + guint16 *payload_len, + gboolean *valid_checksum); + +gboolean goodix_decode_protocol (guint8 *data, + guint32 data_len, + guint8 *cmd, + guint8 **payload, + guint16 *payload_len, + gboolean *valid_checksum, + gboolean *valid_null_checksum); diff --git a/libfprint/drivers/goodixtls/goodixtls.c b/libfprint/drivers/goodixtls/goodixtls.c new file mode 100644 index 000000000..9095d18d7 --- /dev/null +++ b/libfprint/drivers/goodixtls/goodixtls.c @@ -0,0 +1,298 @@ +// Goodix Tls driver for libfprint + +// Copyright (C) 2021 Alexander Meiler +// Copyright (C) 2021 Matthieu CHARETTE +// Copyright (C) 2021 Natasha England-Elbro + +// 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "drivers_api.h" +#include "fp-device.h" +#include "fpi-device.h" +#include "glibconfig.h" +#include "goodix.h" +#include "goodixtls.h" + +#ifndef fpi_device_emulation_mode_enabled +#define fpi_device_emulation_mode_enabled(dev) (g_getenv ("FP_DEVICE_EMULATION") != NULL) +#endif + +static GError * +err_from_ssl (void) +{ + unsigned long code = ERR_get_error (); + const char *msg = ERR_reason_error_string (code); + + return g_error_new (FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL, + "SSL error (0x%lx): %s", code, msg ? msg : "unknown SSL error"); +} + +#include "goodix5xx.h" + +#define GOODIX_TLS_CIPHERS "PSK-AES128-CBC-SHA256:ALL:@SECLEVEL=1" + +static unsigned int +tls_server_psk_server_callback (SSL *ssl, + const char *identity, + unsigned char *psk, + unsigned int max_psk_len) +{ + GoodixTlsServer *server = SSL_get_app_data (ssl); + + if (server && server->user_data) + { + FpDevice *dev = FP_DEVICE (server->user_data); + if (FPI_IS_DEVICE_GOODIXTLS5XX (dev)) + { + FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); + if (cls && cls->psk && cls->psk_len > 0) + { + if (cls->psk_len > max_psk_len) + { + fp_err ("max psk length (%d) too short (needs %d)", max_psk_len, cls->psk_len); + return 0; + } + memcpy (psk, cls->psk, cls->psk_len); + fp_dbg ("5e0a PSK callback: using device-specific PSK (%d bytes, identity='%s')", + cls->psk_len, identity ? identity : ""); + return cls->psk_len; + } + } + else + { + fp_warn ("5e0a PSK callback: dev %p is not GOODIXTLS5XX", dev); + } + } + else + { + fp_warn ("5e0a PSK callback: server (%p) or user_data (%p) is NULL", + server, server ? server->user_data : NULL); + } + + const int len = 32; + + fp_warn ("5e0a PSK callback: fallback to zero PSK (len %d, max %d)", len, max_psk_len); + if (len > max_psk_len) + { + fp_err ("max psk length (%d) too short (needs %d)", max_psk_len, len); + return 0; + } + + // zero out the psk + for (int n = 0; n != len; ++n) + psk[n] = 0; + + return len; +} + +static SSL_CTX * +tls_server_create_ctx (void) +{ + const SSL_METHOD *method; + + method = TLS_server_method (); + + SSL_CTX *ctx = SSL_CTX_new (method); + + if (!ctx) + return NULL; + + return ctx; +} + +static void +tls_server_config_ctx (SSL_CTX *ctx) +{ + (void) SSL_CTX_set_ecdh_auto (ctx, 1); + SSL_CTX_set_dh_auto (ctx, 1); + SSL_CTX_set_cipher_list (ctx, GOODIX_TLS_CIPHERS); + SSL_CTX_set_min_proto_version (ctx, TLS1_2_VERSION); + SSL_CTX_set_max_proto_version (ctx, TLS1_2_VERSION); + SSL_CTX_set_psk_server_callback (ctx, tls_server_psk_server_callback); +} + +int +goodix_tls_client_write (GoodixTlsServer *self, guint8 *data, guint16 length) +{ + return write (self->client_fd, data, length * sizeof (guint8)); +} +int +goodix_tls_client_read (GoodixTlsServer *self, guint8 *data, guint16 length) +{ + return read (self->client_fd, data, length * sizeof (guint8)); +} + +int +goodix_tls_server_read (GoodixTlsServer *self, guint8 *data, + guint32 length, GError **error) +{ + int retr = SSL_read (self->ssl_layer, data, length * sizeof (guint8)); + + if (retr <= 0) + *error = err_from_ssl (); + return retr; +} + +static void +tls_config_ssl (SSL *ssl) +{ + SSL_set_min_proto_version (ssl, TLS1_2_VERSION); + SSL_set_max_proto_version (ssl, TLS1_2_VERSION); + SSL_set_psk_server_callback (ssl, tls_server_psk_server_callback); + SSL_set_cipher_list (ssl, GOODIX_TLS_CIPHERS); +} + +static void * +goodix_tls_init_serve (void *me) +{ + GoodixTlsServer *self = me; + + fp_dbg ("TLS server waiting to accept..."); + int retr = SSL_accept (self->ssl_layer); + + fp_dbg ("TLS server accept done"); + if (retr <= 0) + { + unsigned long err_code; + while ((err_code = ERR_get_error ()) != 0) + { + fp_warn ("5e0a TLS accept failed: %s (0x%lx, cipher: %s)", + ERR_error_string (err_code, NULL), err_code, + SSL_get_cipher_name (self->ssl_layer)); + } + } + else + { + fp_dbg ("5e0a TLS connection ready (cipher: %s, proto: %s)", + SSL_get_cipher_name (self->ssl_layer), + SSL_get_version (self->ssl_layer)); + } + return NULL; +} + +gboolean +goodix_tls_server_deinit (GoodixTlsServer *self, GError **error) +{ + if (!self) + return TRUE; + + /* First shutdown both socket descriptors. + * This immediately unblocks any thread in SSL_accept() or read() with EOF. */ + if (self->client_fd >= 0) + shutdown (self->client_fd, SHUT_RDWR); + if (self->sock_fd >= 0) + shutdown (self->sock_fd, SHUT_RDWR); + + /* Now join the serve thread which unblocks instantly */ + if (self->serve_thread) + { + pthread_join (self->serve_thread, NULL); + self->serve_thread = 0; + } + + /* Close file descriptors after the worker thread has safely exited */ + if (self->client_fd >= 0) + { + close (self->client_fd); + self->client_fd = -1; + } + if (self->sock_fd >= 0) + { + close (self->sock_fd); + self->sock_fd = -1; + } + + if (self->ssl_layer) + { + SSL_shutdown (self->ssl_layer); + SSL_free (self->ssl_layer); + self->ssl_layer = NULL; + } + + if (self->ssl_ctx) + { + SSL_CTX_free (self->ssl_ctx); + self->ssl_ctx = NULL; + } + + return TRUE; +} + +gboolean +goodix_tls_server_init (GoodixTlsServer *self, GError **error) +{ + self->sock_fd = -1; + self->client_fd = -1; + self->serve_thread = 0; + self->ssl_layer = NULL; + self->ssl_ctx = NULL; + + if (self->user_data && fpi_device_emulation_mode_enabled (FP_DEVICE (self->user_data))) + { + static const unsigned char fixed_seed[32] = "goodix5e0a_deterministic_seed_01"; + RAND_seed (fixed_seed, sizeof (fixed_seed)); + } + + SSL_load_error_strings (); + OpenSSL_add_ssl_algorithms (); + SSL_library_init (); + self->ssl_ctx = tls_server_create_ctx (); + if (self->ssl_ctx == NULL) + { + fp_dbg ("Unable to create TLS server context\n"); + *error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Unable to " + "create TLS " + "server " + "context"); + return FALSE; + } + tls_server_config_ctx (self->ssl_ctx); + + int socks[2] = {-1, -1}; + if (socketpair (AF_UNIX, SOCK_STREAM, 0, socks) != 0) + { + g_set_error (error, G_FILE_ERROR, errno, + "failed to create socket pair: %s", strerror (errno)); + SSL_CTX_free (self->ssl_ctx); + self->ssl_ctx = NULL; + return FALSE; + } + self->sock_fd = socks[0]; + self->client_fd = socks[1]; + + self->ssl_layer = SSL_new (self->ssl_ctx); + SSL_set_app_data (self->ssl_layer, self); + tls_config_ssl (self->ssl_layer); + SSL_set_fd (self->ssl_layer, self->sock_fd); + + pthread_create (&self->serve_thread, 0, goodix_tls_init_serve, self); + + return TRUE; +} diff --git a/libfprint/drivers/goodixtls/goodixtls.h b/libfprint/drivers/goodixtls/goodixtls.h new file mode 100644 index 000000000..80480a655 --- /dev/null +++ b/libfprint/drivers/goodixtls/goodixtls.h @@ -0,0 +1,109 @@ +// Goodix Tls driver for libfprint + +// Copyright (C) 2021 Alexander Meiler +// Copyright (C) 2021 Matthieu CHARETTE +// Copyright (C) 2021 Natasha England-Elbro + +// 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 + +#pragma once + +#include + +struct _GoodixTlsServer; + + +/** + * @brief TLS server context for goodix devices + * @details This works by creating an openssl TLS server and a socket for the client and server end. + * Raw data is read and written on the client-side and is intended to be passed directly to and from + * the sensor (so it acts as the client). The server-side meanwhile provides a way to decrypt the data + * from the sensor by reading out of the server socket with openssl + * @struct GoodixTlsServer + */ +typedef struct _GoodixTlsServer +{ + gpointer user_data; // Passed to all callbacks + + SSL_CTX *ssl_ctx; + SSL *ssl_layer; + + int sock_fd; + int client_fd; + + pthread_t serve_thread; +} GoodixTlsServer; + +/** + * @brief Initalise the server + * + * @param self context to init + * @param error output error + * @return gboolean TRUE on success, FALSE otherwise + */ +gboolean goodix_tls_server_init (GoodixTlsServer *self, + GError **error); + +/** + * @brief Read a message from the server side of the TLS connection. + * i.e. decrypt the message last written with goodix_tls_client_write() + * + * @param self server context + * @param data buffer to read the data into + * @param length length of the data expected, buffer should be at least this size + * @param error output error + * @return int bytes read or <=0 in case of error + */ +int goodix_tls_server_read (GoodixTlsServer *self, + guint8 *data, + guint32 length, + GError **error); + +/** + * @brief Write a message directly to the client end of the TLS connection. + * This should be used to dump encrypted data directly from the device + * to be decrypted by goodix_tls_server_read() + * + * @param self + * @param data buffer to write + * @param length length of data to be written + * @return int bytes written or -1 in case of error + */ +int goodix_tls_client_write (GoodixTlsServer *self, + guint8 *data, + guint16 length); + +/** + * @brief Read an encrypted response from the client end of the TLS connection. + * This is needed for e.g. handshaking + * + * @param self + * @param data buffer to read into + * @param length length of buffer + * @return int bytes read or -1 for error or 0 for EOF + */ +int goodix_tls_client_read (GoodixTlsServer *self, + guint8 *data, + guint16 length); + +/** + * @brief Shutdown the TLS server + * + * @param self context to shutdown + * @param error output error + * @return gboolean TRUE on success, FALSE otherwise + */ +gboolean goodix_tls_server_deinit (GoodixTlsServer *self, + GError **error); diff --git a/libfprint/fprint-list-udev-hwdb.c b/libfprint/fprint-list-udev-hwdb.c index cf1c4ecff..4ee77ac3e 100644 --- a/libfprint/fprint-list-udev-hwdb.c +++ b/libfprint/fprint-list-udev-hwdb.c @@ -118,7 +118,6 @@ static const FpIdEntry whitelist_id_table[] = { { .vid = 0x27c6, .pid = 0x55a4 }, { .vid = 0x27c6, .pid = 0x55b4 }, { .vid = 0x27c6, .pid = 0x5740 }, - { .vid = 0x27c6, .pid = 0x5e0a }, { .vid = 0x27c6, .pid = 0x581a }, { .vid = 0x2808, .pid = 0x9338 }, { .vid = 0x2808, .pid = 0x93a9 }, diff --git a/libfprint/meson.build b/libfprint/meson.build index d3c8b034c..7b74b6885 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -139,6 +139,8 @@ driver_sources = { [ 'drivers/synaptics/synaptics.c', 'drivers/synaptics/bmkt_message.c' ], 'goodixmoc' : [ 'drivers/goodixmoc/goodix.c', 'drivers/goodixmoc/goodix_proto.c' ], + 'goodixtls5e0a' : + [ 'drivers/goodixtls/goodix5e0a.c' ], 'fpcmoc' : [ 'drivers/fpcmoc/fpc.c' ], } @@ -150,6 +152,10 @@ helper_sources = { [ 'drivers/aesx660.c' ], 'aes3k' : [ 'drivers/aes3k.c' ], + 'goodixtls' : + [ 'drivers/goodixtls/goodix_proto.c', 'drivers/goodixtls/goodix.c', 'drivers/goodixtls/goodixtls.c', 'drivers/goodixtls/goodix5xx.c' ], + 'openssl' : + [ ], 'nss' : [ ], 'udev' : diff --git a/meson.build b/meson.build index 1badb1644..3c73621b0 100644 --- a/meson.build +++ b/meson.build @@ -124,6 +124,7 @@ default_drivers = [ 'upeksonly', 'upekts', 'goodixmoc', + 'goodixtls5e0a', 'nb1010', 'fpcmoc', @@ -157,6 +158,7 @@ driver_helper_mapping = { 'aes3500' : [ 'aeslib', 'aes3k' ], 'aes4000' : [ 'aeslib', 'aes3k' ], 'uru4000' : [ 'nss' ], + 'goodixtls5e0a' : [ 'goodixtls', 'openssl' ], 'elanspi' : [ 'udev' ], 'virtual_image' : [ 'virtual' ], 'virtual_device' : [ 'virtual' ], @@ -220,6 +222,13 @@ foreach i : driver_helpers endif optional_deps += nss_dep + elif i == 'openssl' + openssl_dep = dependency('openssl', version: '>= 3.0', required: false) + if not openssl_dep.found() + error('OpenSSL is required for @0@ and possibly others'.format(driver)) + endif + + optional_deps += openssl_dep elif i == 'udev' install_udev_rules = true From 36f6e552053c06dab6eb629c9802a6109f0ef25f Mon Sep 17 00:00:00 2001 From: Nix User Date: Sun, 6 Sep 2026 18:02:23 +0530 Subject: [PATCH 02/17] goodixtls: fail activation when TLS accept fails instead of running dead The proxy SSM completing never proved the openssl server accepted: on accept failure (e.g. peer Finished bad-record-mac when the device negotiates with an unexpected key) the serve thread logged and exited while activation proceeded on a dead session, hanging later at FDT with a command timeout. Record the accept outcome in GoodixTlsServer and gate tls_handshake_done on it, failing activation with a clear TLS error through the normal ready-callback path. --- libfprint/drivers/goodixtls/goodix.c | 69 +++++++++++++++++++++++++ libfprint/drivers/goodixtls/goodixtls.c | 19 ++++++- libfprint/drivers/goodixtls/goodixtls.h | 9 ++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/libfprint/drivers/goodixtls/goodix.c b/libfprint/drivers/goodixtls/goodix.c index d06b440b2..08bedcab1 100644 --- a/libfprint/drivers/goodixtls/goodix.c +++ b/libfprint/drivers/goodixtls/goodix.c @@ -1455,6 +1455,45 @@ on_tls_successfully_established (FpDevice *dev, gpointer user_data, dev, priv->tls_ready_callback->user_data, NULL); g_clear_pointer (&priv->tls_ready_callback, g_free); } + +/* Wait briefly for the TLS serve thread to finish SSL_accept, then report + * whether the device completed the handshake. In the healthy case accept + * returns right after the proxied client Finished, well before the proxy + * SSM ends, so this returns immediately; the deadline only bounds genuinely + * stuck handshakes. If the outcome is still unknown at the deadline, return + * TRUE to preserve the previous behavior (never fail a slow-but-healthy + * handshake on a missing signal). */ +static gboolean +tls_accept_wait_ok (FpDevice *dev) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + if (!priv->tls_hop) + return TRUE; + + for (int i = 0; + i < 200 && !g_atomic_int_get (&priv->tls_hop->accept_done); + i++) + g_usleep (10000); + + if (!g_atomic_int_get (&priv->tls_hop->accept_done)) + { + fp_dbg ("TLS accept outcome not ready yet, proceeding"); + return TRUE; + } + + if (priv->tls_hop->accept_ret <= 0) + { + fp_err ("TLS not accepted by device: %s", + priv->tls_hop->accept_err); + return FALSE; + } + + return TRUE; +} + static void tls_handshake_done (FpiSsm *ssm, FpDevice *dev, GError *error) { @@ -1473,6 +1512,36 @@ tls_handshake_done (FpiSsm *ssm, FpDevice *dev, GError *error) } return; } + + /* The proxy SSM completing does not prove the openssl server accepted: + * check the serve thread outcome before declaring the session live. + * Without this, an accept failure (e.g. peer Finished bad-record-mac from + * a device key the host does not expect) went unnoticed and activation + * proceeded on a dead session, hanging later at FDT with a command + * timeout instead of failing here with a clear TLS error. */ + if (!tls_accept_wait_ok (dev)) + { + GError *accept_error = fpi_device_error_new_msg ( + FP_DEVICE_ERROR_GENERAL, + "TLS handshake failed: device did not complete the handshake " + "(likely PSK mismatch; see log for accept error)"); + fp_err ("%s", accept_error->message); + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + if (priv->tls_ready_callback) + { + ((GoodixNoneCallback) priv->tls_ready_callback->callback)( + dev, priv->tls_ready_callback->user_data, accept_error); + g_clear_pointer (&priv->tls_ready_callback, g_free); + } + else + { + g_error_free (accept_error); + } + return; + } + goodix_send_tls_successfully_established ( dev, on_tls_successfully_established, NULL); } diff --git a/libfprint/drivers/goodixtls/goodixtls.c b/libfprint/drivers/goodixtls/goodixtls.c index 9095d18d7..f5c04283f 100644 --- a/libfprint/drivers/goodixtls/goodixtls.c +++ b/libfprint/drivers/goodixtls/goodixtls.c @@ -178,15 +178,28 @@ goodix_tls_init_serve (void *me) int retr = SSL_accept (self->ssl_layer); fp_dbg ("TLS server accept done"); + self->accept_ret = retr; if (retr <= 0) { unsigned long err_code; + gboolean first = TRUE; while ((err_code = ERR_get_error ()) != 0) { + const char *err_str = ERR_error_string (err_code, NULL); + if (first) + { + g_snprintf (self->accept_err, sizeof (self->accept_err), + "%s (0x%lx)", err_str ? err_str : "unknown error", + err_code); + first = FALSE; + } fp_warn ("5e0a TLS accept failed: %s (0x%lx, cipher: %s)", - ERR_error_string (err_code, NULL), err_code, + err_str, err_code, SSL_get_cipher_name (self->ssl_layer)); } + if (first) + g_snprintf (self->accept_err, sizeof (self->accept_err), + "SSL_accept returned %d with no queued error", retr); } else { @@ -194,6 +207,7 @@ goodix_tls_init_serve (void *me) SSL_get_cipher_name (self->ssl_layer), SSL_get_version (self->ssl_layer)); } + g_atomic_int_set (&self->accept_done, 1); return NULL; } @@ -253,6 +267,9 @@ goodix_tls_server_init (GoodixTlsServer *self, GError **error) self->serve_thread = 0; self->ssl_layer = NULL; self->ssl_ctx = NULL; + self->accept_done = 0; + self->accept_ret = 0; + self->accept_err[0] = '\0'; if (self->user_data && fpi_device_emulation_mode_enabled (FP_DEVICE (self->user_data))) { diff --git a/libfprint/drivers/goodixtls/goodixtls.h b/libfprint/drivers/goodixtls/goodixtls.h index 80480a655..3abf4720c 100644 --- a/libfprint/drivers/goodixtls/goodixtls.h +++ b/libfprint/drivers/goodixtls/goodixtls.h @@ -44,6 +44,15 @@ typedef struct _GoodixTlsServer int client_fd; pthread_t serve_thread; + + /* SSL_accept outcome, recorded by the serve thread before it exits. + * accept_done is an atomic flag; accept_ret/accept_err are valid once set. + * Lets the handshake completion path fail activation loudly instead of + * running on a dead session when the device negotiates with a key the + * host does not expect (e.g. peer Finished bad-record-mac). */ + volatile gint accept_done; + int accept_ret; + char accept_err[256]; } GoodixTlsServer; /** From a7e80e96658f1b96129849679e3671e34bd4765d Mon Sep 17 00:00:00 2001 From: Nix User Date: Sun, 6 Sep 2026 18:51:52 +0530 Subject: [PATCH 03/17] goodixtls: fix error handling, lifecycle safety, and header hygiene - goodix_send_data: bound chunk length on final partial USB packet to prevent out-of-bounds reads - goodix_tls_init: handle goodix_tls_server_init failure by reporting error to callback and releasing resources cleanly - dev_init/dev_deinit: fix inverted boolean check on goodix_dev_init and goodix_dev_deinit to properly propagate errors - goodix_dev_deinit: avoid double-free of priv->data and unref priv->transfer_cancel_tkn - goodix_tls_server_read: check for NULL error pointer before assigning - goodix_tls_server_init: use g_set_error and validate pthread_create - goodixtls: track serve_thread with boolean flag instead of scalar cast - goodixtls: clean up headers, add missing includes (stdlib.h, unistd.h, openssl/ssl.h, pthread.h) and remove unused ones --- libfprint/drivers/goodixtls/goodix.c | 29 ++++++++++++++++------ libfprint/drivers/goodixtls/goodix5xx.c | 5 ++-- libfprint/drivers/goodixtls/goodixtls.c | 33 +++++++++++++------------ libfprint/drivers/goodixtls/goodixtls.h | 3 +++ 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/libfprint/drivers/goodixtls/goodix.c b/libfprint/drivers/goodixtls/goodix.c index 08bedcab1..bff96c30b 100644 --- a/libfprint/drivers/goodixtls/goodix.c +++ b/libfprint/drivers/goodixtls/goodix.c @@ -555,11 +555,12 @@ goodix_send_data (FpDevice *dev, guint8 *data, guint32 length, for (guint32 i = 0; i < length; i += GOODIX_EP_OUT_MAX_BUF_SIZE) { + guint32 chunk_len = MIN ((guint32) GOODIX_EP_OUT_MAX_BUF_SIZE, length - i); FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); transfer->short_is_error = TRUE; fpi_usb_transfer_fill_bulk_full (transfer, class->ep_out, data + i, - GOODIX_EP_OUT_MAX_BUF_SIZE, NULL); + chunk_len, NULL); if (!fpi_usb_transfer_submit_sync (transfer, GOODIX_TIMEOUT, error)) @@ -1361,11 +1362,13 @@ goodix_dev_deinit (FpDevice *dev, GError **error) /* Teardown entry: orphan any in-flight TLS activation. */ goodix_activation_gen_bump (dev); - if (priv->timeout) - g_source_destroy (priv->timeout); - g_free (priv->data); g_cancellable_cancel (priv->transfer_cancel_tkn); - goodix_shutdown_tls (dev, error); + g_clear_object (&priv->transfer_cancel_tkn); + + g_autoptr(GError) tls_err = NULL; + goodix_shutdown_tls (dev, &tls_err); + if (tls_err) + fp_warn ("TLS shutdown warning: %s", tls_err->message); goodix_reset_state (dev); priv->inited = FALSE; @@ -1662,8 +1665,20 @@ goodix_tls_init (FpDevice *dev, GoodixNoneCallback callback, gpointer user_data) GError *err = NULL; if (!goodix_tls_server_init (priv->tls_hop, &err)) { - fp_err ("failed to init tls server, error: %s, code: %d", err->message, - err->code); + fp_err ("failed to init tls server, error: %s, code: %d", + err ? err->message : "unknown", + err ? err->code : 0); + if (priv->tls_ready_callback) + { + ((GoodixNoneCallback) priv->tls_ready_callback->callback) ( + dev, priv->tls_ready_callback->user_data, err); + g_clear_pointer (&priv->tls_ready_callback, g_free); + } + else + { + g_clear_error (&err); + } + g_clear_pointer (&priv->tls_hop, g_free); return; } diff --git a/libfprint/drivers/goodixtls/goodix5xx.c b/libfprint/drivers/goodixtls/goodix5xx.c index 79e322af6..9b1ef5576 100644 --- a/libfprint/drivers/goodixtls/goodix5xx.c +++ b/libfprint/drivers/goodixtls/goodix5xx.c @@ -27,6 +27,7 @@ #include "drivers_api.h" #include "goodix.h" #include +#include #include @@ -527,7 +528,7 @@ dev_deinit (FpImageDevice * img_dev) FpDevice *dev = FP_DEVICE (img_dev); GError *error = NULL; - if (goodix_dev_deinit (dev, &error)) + if (!goodix_dev_deinit (dev, &error)) { fpi_image_device_close_complete (img_dev, error); return; @@ -541,7 +542,7 @@ dev_init (FpImageDevice *img_dev) FpDevice *dev = FP_DEVICE (img_dev); GError *error = NULL; - if (goodix_dev_init (dev, &error)) + if (!goodix_dev_init (dev, &error)) { fpi_image_device_open_complete (img_dev, error); return; diff --git a/libfprint/drivers/goodixtls/goodixtls.c b/libfprint/drivers/goodixtls/goodixtls.c index f5c04283f..aa971b4c3 100644 --- a/libfprint/drivers/goodixtls/goodixtls.c +++ b/libfprint/drivers/goodixtls/goodixtls.c @@ -18,26 +18,23 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -#include #include #include -#include #include #include #include #include #include -#include #include -#include #include #include +#include #include "drivers_api.h" #include "fp-device.h" #include "fpi-device.h" -#include "glibconfig.h" #include "goodix.h" +#include "goodix5xx.h" #include "goodixtls.h" #ifndef fpi_device_emulation_mode_enabled @@ -54,8 +51,6 @@ err_from_ssl (void) "SSL error (0x%lx): %s", code, msg ? msg : "unknown SSL error"); } -#include "goodix5xx.h" - #define GOODIX_TLS_CIPHERS "PSK-AES128-CBC-SHA256:ALL:@SECLEVEL=1" static unsigned int @@ -155,7 +150,7 @@ goodix_tls_server_read (GoodixTlsServer *self, guint8 *data, { int retr = SSL_read (self->ssl_layer, data, length * sizeof (guint8)); - if (retr <= 0) + if (retr <= 0 && error) *error = err_from_ssl (); return retr; } @@ -225,10 +220,10 @@ goodix_tls_server_deinit (GoodixTlsServer *self, GError **error) shutdown (self->sock_fd, SHUT_RDWR); /* Now join the serve thread which unblocks instantly */ - if (self->serve_thread) + if (self->serve_thread_started) { pthread_join (self->serve_thread, NULL); - self->serve_thread = 0; + self->serve_thread_started = FALSE; } /* Close file descriptors after the worker thread has safely exited */ @@ -264,7 +259,7 @@ goodix_tls_server_init (GoodixTlsServer *self, GError **error) { self->sock_fd = -1; self->client_fd = -1; - self->serve_thread = 0; + self->serve_thread_started = FALSE; self->ssl_layer = NULL; self->ssl_ctx = NULL; self->accept_done = 0; @@ -284,10 +279,8 @@ goodix_tls_server_init (GoodixTlsServer *self, GError **error) if (self->ssl_ctx == NULL) { fp_dbg ("Unable to create TLS server context\n"); - *error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Unable to " - "create TLS " - "server " - "context"); + g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL, + "Unable to create TLS server context"); return FALSE; } tls_server_config_ctx (self->ssl_ctx); @@ -309,7 +302,15 @@ goodix_tls_server_init (GoodixTlsServer *self, GError **error) tls_config_ssl (self->ssl_layer); SSL_set_fd (self->ssl_layer, self->sock_fd); - pthread_create (&self->serve_thread, 0, goodix_tls_init_serve, self); + if (pthread_create (&self->serve_thread, NULL, goodix_tls_init_serve, self) == 0) + self->serve_thread_started = TRUE; + else + { + g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL, + "Failed to create TLS serve thread"); + goodix_tls_server_deinit (self, NULL); + return FALSE; + } return TRUE; } diff --git a/libfprint/drivers/goodixtls/goodixtls.h b/libfprint/drivers/goodixtls/goodixtls.h index 3abf4720c..d5ffe1375 100644 --- a/libfprint/drivers/goodixtls/goodixtls.h +++ b/libfprint/drivers/goodixtls/goodixtls.h @@ -21,6 +21,8 @@ #pragma once #include +#include +#include struct _GoodixTlsServer; @@ -44,6 +46,7 @@ typedef struct _GoodixTlsServer int client_fd; pthread_t serve_thread; + gboolean serve_thread_started; /* SSL_accept outcome, recorded by the serve thread before it exits. * accept_done is an atomic flag; accept_ret/accept_err are valid once set. From 2400a8ff2b0272e33c876a93b50ccff99c3acd12 Mon Sep 17 00:00:00 2001 From: Nix User Date: Sun, 6 Sep 2026 19:07:22 +0530 Subject: [PATCH 04/17] goodixtls: guard cancellable nullability and ensure complete socket I/O - goodix_receive_data_cb / goodix_start_read_loop: guard against NULL transfer_cancel_tkn during device deinit/re-init - goodix_tls_client_write: loop to handle short writes and retry on EINTR - goodix_tls_client_read: guard descriptors and retry on EINTR while preserving stream socket read semantics for multi-record TLS flights --- libfprint/drivers/goodixtls/goodix.c | 4 +-- libfprint/drivers/goodixtls/goodixtls.c | 36 +++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/libfprint/drivers/goodixtls/goodix.c b/libfprint/drivers/goodixtls/goodix.c index bff96c30b..e341fe2e4 100644 --- a/libfprint/drivers/goodixtls/goodix.c +++ b/libfprint/drivers/goodixtls/goodix.c @@ -449,7 +449,7 @@ goodix_receive_data_cb (FpiUsbTransfer *transfer, FpDevice *dev, FpiDeviceGoodixTlsPrivate *priv = fpi_device_goodixtls_get_instance_private (self); - if (g_cancellable_is_cancelled (priv->transfer_cancel_tkn) || + if ((priv->transfer_cancel_tkn && g_cancellable_is_cancelled (priv->transfer_cancel_tkn)) || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { fp_dbg ("transfer cancelled, aborting read loop..."); @@ -496,7 +496,7 @@ goodix_start_read_loop (FpDevice *dev) if (priv->inited) return; - if (g_cancellable_is_cancelled (priv->transfer_cancel_tkn)) + if (priv->transfer_cancel_tkn && g_cancellable_is_cancelled (priv->transfer_cancel_tkn)) g_cancellable_reset (priv->transfer_cancel_tkn); priv->inited = TRUE; diff --git a/libfprint/drivers/goodixtls/goodixtls.c b/libfprint/drivers/goodixtls/goodixtls.c index aa971b4c3..8ab6d1c70 100644 --- a/libfprint/drivers/goodixtls/goodixtls.c +++ b/libfprint/drivers/goodixtls/goodixtls.c @@ -136,12 +136,44 @@ tls_server_config_ctx (SSL_CTX *ctx) int goodix_tls_client_write (GoodixTlsServer *self, guint8 *data, guint16 length) { - return write (self->client_fd, data, length * sizeof (guint8)); + if (!self || self->client_fd < 0) + return -1; + + size_t total_written = 0; + + while (total_written < length) + { + ssize_t ret = write (self->client_fd, data + total_written, length - total_written); + + if (ret < 0) + { + if (errno == EINTR) + continue; + return -1; + } + if (ret == 0) + break; + total_written += ret; + } + + return (int) total_written; } + int goodix_tls_client_read (GoodixTlsServer *self, guint8 *data, guint16 length) { - return read (self->client_fd, data, length * sizeof (guint8)); + if (!self || self->client_fd < 0) + return -1; + + ssize_t ret; + + do + { + ret = read (self->client_fd, data, length * sizeof (guint8)); + } + while (ret < 0 && errno == EINTR); + + return (int) ret; } int From 203cfc84d42005320aeb59382e46e8dd76896304 Mon Sep 17 00:00:00 2001 From: Nix User Date: Sun, 6 Sep 2026 19:24:01 +0530 Subject: [PATCH 05/17] goodix5e0a: arm command timeout on FDT_DOWN polling Passing timeout 0 disabled the USB command timeout in goodix_send_protocol. If the MCU dropped a packet, crashed, or was unplugged, the scan state machine hung indefinitely. Use GOODIX_TIMEOUT (1000ms) as a watchdog on FDT_DOWN command replies without altering the 50ms polling cadence. --- libfprint/drivers/goodixtls/goodix5e0a.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c index 4cbd7d79b..a409d49b8 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.c +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -284,7 +284,7 @@ goodix5e0a_on_down_poll_timeout (FpDevice *dev, gpointer user_data) send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, goodix_5e0a_down_s12, sizeof (goodix_5e0a_down_s12), - 0, goodix5e0a_on_fdt_down_reply, ssm); + GOODIX_TIMEOUT, goodix5e0a_on_fdt_down_reply, ssm); } static void @@ -535,7 +535,7 @@ goodix5e0a_scan_run_state (FpiSsm *ssm, FpDevice *dev) case SCAN_5E0A_FDT_DOWN: send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, goodix_5e0a_down_s12, sizeof (goodix_5e0a_down_s12), - 0, goodix5e0a_on_fdt_down_reply, ssm); + GOODIX_TIMEOUT, goodix5e0a_on_fdt_down_reply, ssm); break; case SCAN_5E0A_GET_IMAGE: From 1e1ba3bc0b026a31e514b8029718c7f327939654 Mon Sep 17 00:00:00 2001 From: Nix User Date: Sun, 6 Sep 2026 20:23:34 +0530 Subject: [PATCH 06/17] goodixtls: clean up informal comments, section banners, and dead TODOs --- libfprint/drivers/goodixtls/goodix.c | 103 +++++++-------------- libfprint/drivers/goodixtls/goodix.h | 65 +++++-------- libfprint/drivers/goodixtls/goodix5e0a.c | 49 ++++------ libfprint/drivers/goodixtls/goodix5e0a.h | 41 ++++---- libfprint/drivers/goodixtls/goodix5xx.c | 40 ++++---- libfprint/drivers/goodixtls/goodix5xx.h | 59 ++++++------ libfprint/drivers/goodixtls/goodix_proto.c | 39 ++++---- libfprint/drivers/goodixtls/goodix_proto.h | 39 ++++---- libfprint/drivers/goodixtls/goodixtls.c | 43 +++++---- libfprint/drivers/goodixtls/goodixtls.h | 41 ++++---- 10 files changed, 229 insertions(+), 290 deletions(-) diff --git a/libfprint/drivers/goodixtls/goodix.c b/libfprint/drivers/goodixtls/goodix.c index e341fe2e4..2a91c0f7d 100644 --- a/libfprint/drivers/goodixtls/goodix.c +++ b/libfprint/drivers/goodixtls/goodix.c @@ -1,22 +1,23 @@ -// Goodix Tls driver for libfprint - -// Copyright (C) 2021 Alexander Meiler -// Copyright (C) 2021 Matthieu CHARETTE -// Copyright (C) 2021 Natasha England-Elbro - -// 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 +/* Goodix TLS driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * Copyright (C) 2021 Natasha England-Elbro + * + * 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 + */ #include "fpi-log.h" #include "fpi-ssm.h" @@ -66,9 +67,6 @@ typedef struct G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (FpiDeviceGoodixTls, fpi_device_goodixtls, FP_TYPE_IMAGE_DEVICE); -// TODO remove every GDestroyNotify -// TODO add cmd timeouts - gchar * data_to_str (guint8 *data, guint32 length) { @@ -80,8 +78,6 @@ data_to_str (guint8 *data, guint32 length) return string; } -// ---- GOODIX RECEIVE SECTION START ---- - void goodix_receive_done (FpDevice *dev, guint8 *data, guint16 length, GError *error) @@ -127,7 +123,7 @@ goodix_receive_none_tolerant (FpDevice *dev, guint8 *data, guint16 length, GoodixNoneCallback callback = (GoodixNoneCallback) cb_info->callback; if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) - g_clear_error (&error); /* ponytail: NOP silence = buffer already empty */ + g_clear_error (&error); /* Flush error ignored: buffer already empty */ callback (dev, cb_info->user_data, error); } @@ -188,7 +184,7 @@ goodix_receive_reset (FpDevice *dev, guint8 *data, guint16 length, } callback (dev, data[0] == 0x00 ? FALSE : TRUE, - GUINT16_FROM_LE (*(guint16 *) (data + sizeof (guint8))), // TODO + GUINT16_FROM_LE (*(guint16 *) (data + sizeof (guint8))), cb_info->user_data, NULL); } @@ -289,7 +285,7 @@ goodix_receive_firmware_version (FpDevice *dev, guint8 *data, memcpy (payload, data, length); - // Some device send the firmware without the null terminator + /* Some devices send the firmware without the null terminator. */ payload[length] = 0x00; callback (dev, payload, cb_info->user_data, NULL); @@ -313,7 +309,6 @@ goodix_receive_ack (FpDevice *dev, guint8 *data, guint16 length, if (!ack->always_true) { - // Warn about error. fp_warn ("Invalid ACK flags: 0x%02x", data[sizeof (guint8)]); return; } @@ -354,14 +349,12 @@ goodix_receive_protocol (FpDevice *dev, guint8 *data, guint32 length) guint8 cmd; g_autofree guint8 *payload = NULL; guint16 payload_len; - gboolean valid_checksum, valid_null_checksum; // TODO implement checksum. + gboolean valid_checksum, valid_null_checksum; if (!goodix_decode_protocol (data, length, &cmd, &payload, &payload_len, &valid_checksum, &valid_null_checksum)) { fp_err ("Incomplete, size: %d", length); - // Protocol is not full, we still need data. - // TODO implement protocol assembling. return; } @@ -399,7 +392,7 @@ goodix_receive_pack (FpDevice *dev, guint8 *data, guint32 length) guint8 flags; g_autofree guint8 *payload = NULL; guint16 payload_len; - gboolean valid_checksum; // TODO implement checksum. + gboolean valid_checksum; priv->data = g_realloc (priv->data, priv->length + length); memcpy (priv->data + priv->length, data, length); @@ -408,7 +401,6 @@ goodix_receive_pack (FpDevice *dev, guint8 *data, guint32 length) if (!goodix_decode_pack (priv->data, priv->length, &flags, &payload, &payload_len, &valid_checksum)) { - // Packet is not full, we still need data. fp_dbg ("not full packet"); return; } @@ -459,11 +451,9 @@ goodix_receive_data_cb (FpiUsbTransfer *transfer, FpDevice *dev, } if (error) { - // Warn about error and free it. fp_warn ("Receive data error: %s", error->message); g_error_free (error); - // Retry receiving data and return. goodix_receive_data (dev); return; } @@ -540,12 +530,6 @@ goodix_receive_data (FpDevice *dev) goodix_receive_data_cb, NULL); } -// ---- GOODIX RECEIVE SECTION END ---- - -// ----------------------------------------------------------------------------- - -// ---- GOODIX SEND SECTION START ---- - gboolean goodix_send_data (FpDevice *dev, guint8 *data, guint32 length, GDestroyNotify free_func, GError **error) @@ -608,11 +592,10 @@ goodix_send_protocol ( if (priv->ack || priv->reply || priv->timeout) { - // A command is already running. fp_warn ("A command is already running: 0x%02x", priv->cmd); if (free_func) free_func ((void *) payload); - // ponytail: fail loudly so the waiting SSM aborts instead of hanging + /* Fail loudly so the waiting SSM aborts instead of hanging. */ GError *collision_error = g_error_new (G_IO_ERROR, G_IO_ERROR_BUSY, "A command is already running: 0x%02x", priv->cmd); @@ -651,8 +634,7 @@ goodix_send_nop (FpDevice *dev, GoodixNoneCallback callback, GoodixNop payload = {.unknown = 0x00000000}; GoodixCallbackInfo *cb_info; - /* ponytail: flush, not a handshake — silence is success (see tolerant - receiver); a real ACK is still validated when one arrives. */ + /* Flush command: silence from the MCU indicates success. */ if (callback) { cb_info = malloc (sizeof (GoodixCallbackInfo)); @@ -854,7 +836,7 @@ goodix_send_write_sensor_register (FpDevice *dev, guint16 address, GoodixNoneCallback callback, gpointer user_data) { - // Only support one address and one value + /* Only support one address and one value. */ GoodixWriteSensorRegister payload = {.multiples = FALSE, .address = GUINT16_TO_LE (address), @@ -885,7 +867,7 @@ goodix_send_read_sensor_register (FpDevice *dev, guint16 address, GoodixDefaultCallback callback, gpointer user_data) { - // Only support one address + /* Only support one address. */ GoodixReadSensorRegister payload = { .multiples = FALSE, .address = GUINT16_TO_LE (address), .length = length @@ -993,7 +975,7 @@ void goodix_send_reset (FpDevice *dev, gboolean reset_sensor, guint8 sleep_time, GoodixResetCallback callback, gpointer user_data) { - // Only support reset sensor + /* Only support reset sensor. */ GoodixReset payload = {.soft_reset_mcu = FALSE, .reset_sensor = reset_sensor ? TRUE : FALSE, @@ -1109,8 +1091,7 @@ goodix_send_tls_successfully_established (FpDevice *dev, cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; - // special case: timeout needs to be at least 10ms and it will always timeout for some reason - // todo: work out why it always times out for this but not the python driver + /* Timeout needs to be at least 10ms. */ goodix_send_protocol (dev, GOODIX_CMD_TLS_SUCCESSFULLY_ESTABLISHED, (guint8 *) &payload, sizeof (payload), NULL, TRUE, @@ -1127,7 +1108,7 @@ void goodix_send_set_drv_state (FpDevice *dev, GoodixNoneCallback cb, gpointer ud) { - // ponytail: reuse 2-byte helper for the 01 00 payload (goodix.py:611-622) + /* Send 2-byte payload [0x01, 0x00]. */ GoodixDefault payload = {.unused_flags = 0x01}; GoodixCallbackInfo *cb_info; @@ -1227,7 +1208,7 @@ goodix_send_preset_psk_write (FpDevice *dev, guint32 flags, guint8 *psk, GoodixSuccessCallback callback, gpointer user_data) { - // Only support one flags, one payload and one length + /* Only support one flag, one payload, and one length. */ guint8 *payload = g_malloc (sizeof (GoodixPresetPsk) + length); GoodixPresetPsk *preset_psk = (GoodixPresetPsk *) payload; @@ -1284,12 +1265,6 @@ goodix_send_preset_psk_read (FpDevice *dev, guint32 flags, guint16 length, NULL); } -// ---- GOODIX SEND SECTION END ---- - -// ----------------------------------------------------------------------------- - -// ---- DEV SECTION START ---- - gboolean goodix_dev_init (FpDevice *dev, GError **error) { @@ -1377,12 +1352,6 @@ goodix_dev_deinit (FpDevice *dev, GError **error) class->interface, 0, error); } -// ---- DEV SECTION END ---- - -// ----------------------------------------------------------------------------- - -// ---- TLS SECTION START ---- - void goodix_read_tls (FpDevice *dev, GoodixTlsCallback callback, gpointer user_data) @@ -1410,8 +1379,6 @@ on_goodix_tls_read_handshake (FpDevice *dev, guint8 *data, guint16 length, gpointer user_data, GError *error) { - // goodix_tls_handshake_state* state = (goodix_tls_handshake_state*) - // user_data; FpiSsm *ssm = user_data; if (error) @@ -1579,7 +1546,7 @@ tls_handshake_run (FpiSsm *ssm, FpDevice *dev) } else if (stage < TLS_HANDSHAKE_STAGE_CHANGE_CIPHER_S) { - // Still proxying from hardware + /* Still proxying from hardware. */ fpi_ssm_set_data (ssm, dev, NULL); goodix_read_tls (dev, on_goodix_tls_read_handshake, ssm); } @@ -1773,8 +1740,6 @@ goodix_tls_read_image (FpDevice *dev, GoodixImageCallback callback, goodix_send_mcu_get_image (dev, goodix_tls_ready_image_handler, cb_info); } -// ---- TLS SECTION END ---- - static void fpi_device_goodixtls_init (FpiDeviceGoodixTls *self) { diff --git a/libfprint/drivers/goodixtls/goodix.h b/libfprint/drivers/goodixtls/goodix.h index d8c496bab..10a2f3c4a 100644 --- a/libfprint/drivers/goodixtls/goodix.h +++ b/libfprint/drivers/goodixtls/goodix.h @@ -1,32 +1,32 @@ -// Goodix Tls driver for libfprint - -// Copyright (C) 2021 Alexander Meiler -// Copyright (C) 2021 Matthieu CHARETTE -// Copyright (C) 2021 Natasha England-Elbro - -// 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 +/* Goodix TLS driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * Copyright (C) 2021 Natasha England-Elbro + * + * 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 + */ #pragma once #include "drivers_api.h" -// 1 seconds USB timeout +/* 1 second USB timeout */ #define GOODIX_TIMEOUT (1000) -/* ponytail: NOP is a flush — the MCU is routinely silent on it (reference - tolerates no-reply as success), so it gets a short window, not the full. */ +/* NOP is a flush operation where silence from the MCU is expected. */ #define GOODIX_NOP_TIMEOUT (200) G_DECLARE_DERIVABLE_TYPE (FpiDeviceGoodixTls, fpi_device_goodixtls, FPI, @@ -99,8 +99,6 @@ typedef void (*GoodixImageCallback)(FpDevice *dev, gchar *data_to_str (guint8 *data, guint32 length); -// ---- GOODIX RECEIVE SECTION START ---- - /** * @defgroup goodixrecv Goodix receive functions * These functions are callbacks for receiving data from the device @@ -194,11 +192,6 @@ void goodix_receive_data (FpDevice *dev); */ void goodix_start_read_loop (FpDevice *dev); void goodix_stop_read_loop (FpDevice *dev); -// ---- GOODIX RECEIVE SECTION END ---- - -// ----------------------------------------------------------------------------- - -// ---- GOODIX SEND SECTION START ---- /** * @brief Send raw data to the device over USB @@ -510,12 +503,6 @@ void goodix_send_read_otp (FpDevice *dev, GoodixDefaultCallback callback, gpointer user_data); -// ---- GOODIX SEND SECTION END ---- - -// ----------------------------------------------------------------------------- - -// ---- DEV SECTION START ---- - /** * @brief Claim the resources used for communcation with the device * @@ -555,12 +542,6 @@ void goodix_reset_state (FpDevice *dev); guint goodix_activation_gen_get (FpDevice *dev); guint goodix_activation_gen_bump (FpDevice *dev); -// ---- DEV SECTION END ---- - -// ----------------------------------------------------------------------------- - -// ---- TLS SECTION START ---- - /** * @brief Read a TLS packet from the device * @note You probably won't ever need to call this directly from your driver @@ -604,5 +585,3 @@ gboolean goodix_shutdown_tls (FpDevice *dev, void goodix_tls_read_image (FpDevice *dev, GoodixImageCallback callback, gpointer user_data); - -// ---- TLS SECTION END ---- diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c index a409d49b8..708473bf7 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.c +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -1,21 +1,22 @@ -// Goodix TLS driver for libfprint - 27c6:5e0a (Realme Book / ChicagoH) -// Clean-room reverse engineering from passive USB captures of Windows driver traffic. - -// Copyright (C) 2026 The libfprint Goodix 5e0a contributors - -// 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 +/* Goodix TLS driver for libfprint - 27c6:5e0a (Realme Book / ChicagoH) + * Clean-room reverse engineering from passive USB captures of Windows driver traffic. + * + * Copyright (C) 2026 The libfprint Goodix 5e0a contributors + * + * 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 + */ #include "drivers/goodixtls/goodix5xx.h" #include "fp-device.h" @@ -61,8 +62,6 @@ G_DECLARE_FINAL_TYPE (FpiDeviceGoodixTls5e0a, fpi_device_goodixtls5e0a, FPI, G_DEFINE_TYPE (FpiDeviceGoodixTls5e0a, fpi_device_goodixtls5e0a, FPI_TYPE_DEVICE_GOODIXTLS5XX); -// ---- ACTIVATE SECTION START ---- - enum activate_states { ACTIVATE_READ_AND_NOP, ACTIVATE_RESET, @@ -179,12 +178,6 @@ dev_activate (FpImageDevice *img_dev) activate_complete); } -// ---- ACTIVATE SECTION END ---- - -// ----------------------------------------------------------------------------- - -// ---- SCAN SECTION START (Windows-faithful steady-state port) ---- - enum goodix5e0a_scan_states { SCAN_5E0A_SESSION_AE, SCAN_5E0A_SESSION_D6, @@ -634,8 +627,6 @@ goodix5e0a_deactivate (FpImageDevice *img_dev) fpi_image_device_deactivate_complete (img_dev, tls_err); } -// ---- SCAN SECTION END ---- - static void fpi_device_goodixtls5e0a_init (FpiDeviceGoodixTls5e0a *self) { @@ -941,7 +932,7 @@ fpi_device_goodixtls5e0a_class_init (FpiDeviceGoodixTls5e0aClass * class) dev_class->id_table = goodix_5e0a_id_table; dev_class->nr_enroll_stages = 12; dev_class->scan_type = FP_SCAN_TYPE_PRESS; - dev_class->temp_hot_seconds = -1; // Disable thermal watchdog + dev_class->temp_hot_seconds = -1; /* Disable thermal watchdog */ dev_class->suspend = goodix5e0a_suspend; dev_class->resume = goodix5e0a_resume; diff --git a/libfprint/drivers/goodixtls/goodix5e0a.h b/libfprint/drivers/goodixtls/goodix5e0a.h index 4e564fc87..44d51d2b1 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.h +++ b/libfprint/drivers/goodixtls/goodix5e0a.h @@ -1,21 +1,22 @@ -// Goodix TLS driver for libfprint - 27c6:5e0a (Realme Book / ChicagoH) -// Clean-room reverse engineering from passive USB captures of Windows driver traffic. - -// Copyright (C) 2026 The libfprint Goodix 5e0a contributors - -// 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 +/* Goodix TLS driver for libfprint - 27c6:5e0a (Realme Book / ChicagoH) + * Clean-room reverse engineering from passive USB captures of Windows driver traffic. + * + * Copyright (C) 2026 The libfprint Goodix 5e0a contributors + * + * 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 + */ #pragma once @@ -44,7 +45,7 @@ #define GOODIX_5E0A_ENROLL_MIN_MINUTIAE (12) -// Sensor Analog Front-End (AFE) Gain/Exposure Register Configuration +/* Sensor Analog Front-End (AFE) Gain/Exposure Register Configuration */ #define GOODIX_5E0A_REG_GAIN_EXPOSURE (0x022c) #define GOODIX_5E0A_REG_GAIN_EXPOSURE_VAL (0x0305) /* Little-endian 16-bit: \x05\x03 */ #define GOODIX_5E0A_REG_GAIN_EXPOSURE_CALIB_VAL (0x030a) /* Little-endian 16-bit: \x0a\x03 */ @@ -63,7 +64,7 @@ static const guint8 goodix_5e0a_psk[] = { 0x26, 0x9c, 0xe7, 0x52, 0xd7, 0xa8, 0xb2, 0xab }; -// ChicagoH GF3658 DN3 Configuration (256 bytes, wbdi.dll offset 0x197c50, checksum 0x0e53) +/* ChicagoH GF3658 DN3 Configuration (256 bytes, wbdi.dll offset 0x197c50, checksum 0x0e53) */ static const guint8 goodix_5e0a_config[256] = { 0xb0, 0x11, 0x60, 0x71, 0x2c, 0x9d, 0x2c, 0xc9, 0x1c, 0xe5, 0x18, 0xfd, 0x00, 0xfd, 0x00, 0xfd, 0x03, 0xba, 0x00, 0x01, 0x80, 0xca, 0x00, 0x04, 0x00, 0x84, 0x00, 0x15, 0xb3, 0x86, 0x00, 0x00, diff --git a/libfprint/drivers/goodixtls/goodix5xx.c b/libfprint/drivers/goodixtls/goodix5xx.c index 9b1ef5576..4c7d3ddf8 100644 --- a/libfprint/drivers/goodixtls/goodix5xx.c +++ b/libfprint/drivers/goodixtls/goodix5xx.c @@ -1,23 +1,23 @@ -// Goodix Tls driver for libfprint - -// Copyright (C) 2021 Alexander Meiler -// Copyright (C) 2021 Matthieu CHARETTE -// Copyright (C) 2021 Natasha England-Elbro - -// 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 -// +/* Goodix TLS driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * Copyright (C) 2021 Natasha England-Elbro + * + * 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 + */ #include "fp-image-device.h" #include "fpi-image-device.h" #include "fpi-ssm.h" diff --git a/libfprint/drivers/goodixtls/goodix5xx.h b/libfprint/drivers/goodixtls/goodix5xx.h index 46b18e788..2f3cc59af 100644 --- a/libfprint/drivers/goodixtls/goodix5xx.h +++ b/libfprint/drivers/goodixtls/goodix5xx.h @@ -1,22 +1,23 @@ -// Goodix Tls driver for libfprint - -// Copyright (C) 2021 Alexander Meiler -// Copyright (C) 2021 Matthieu CHARETTE -// Copyright (C) 2021 Natasha England-Elbro - -// 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 +/* Goodix TLS driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * Copyright (C) 2021 Natasha England-Elbro + * + * 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 + */ #pragma once @@ -62,25 +63,25 @@ struct _FpiDeviceGoodixTls5xxClass { FpiDeviceGoodixTlsClass parent; - GoodixTls5xxGetMcuFn get_mcu_cfg; ///< provide the mcu config before fdt commands + GoodixTls5xxGetMcuFn get_mcu_cfg; /* Provide MCU config before FDT commands */ GoodixTls5xxGetMcuFn get_fdt_down_cfg; GoodixTls5xxGetMcuFn get_fdt_up_cfg; - GoodixTls5xxProcessFrameFn process_frame; ///< process a frame after it is decoded (e.g. crop it) - GoodixTls5xxProcessRawFrameFn process_raw_frame; ///< process raw 12-bit ADC frame directly - GoodixTls5xxResetStateFn reset_state; ///< callback to reset the state, may be NULL + GoodixTls5xxProcessFrameFn process_frame; /* Process a frame after decoding (e.g. crop) */ + GoodixTls5xxProcessRawFrameFn process_raw_frame; /* Process raw 12-bit ADC frame directly */ + GoodixTls5xxResetStateFn reset_state; /* Callback to reset state, may be NULL */ - guint16 scan_width; ///< width of the raw scanner image - guint16 scan_height; ///< height of the raw scanner image + guint16 scan_width; /* Width of raw scanner image */ + guint16 scan_height; /* Height of raw scanner image */ - const char * firmware_version; ///< only needed if goodixtls5xx_check_firmware_version() is used + const char * firmware_version; /* For goodixtls5xx_check_firmware_version() */ - /// only needed if goodixtls5xx_check_preset_psk_read() is used + /* For goodixtls5xx_check_preset_psk_read() */ int psk_flags; guint16 psk_len; const guint8 * psk; - int reset_number; ///< only needed if goodixtls5xx_check_reset() is used - gboolean has_calibration; ///< TRUE if device requires calibration step (e.g. 511) + int reset_number; /* For goodixtls5xx_check_reset() */ + gboolean has_calibration; /* TRUE if device requires calibration step (e.g. 511) */ }; /** diff --git a/libfprint/drivers/goodixtls/goodix_proto.c b/libfprint/drivers/goodixtls/goodix_proto.c index b8e6dfbfe..f4419a906 100644 --- a/libfprint/drivers/goodixtls/goodix_proto.c +++ b/libfprint/drivers/goodixtls/goodix_proto.c @@ -1,22 +1,23 @@ -// Goodix Tls driver for libfprint - -// Copyright (C) 2021 Alexander Meiler -// Copyright (C) 2021 Matthieu CHARETTE -// Copyright (C) 2021 Natasha England-Elbro - -// 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 +/* Goodix TLS driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * Copyright (C) 2021 Natasha England-Elbro + * + * 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 + */ #include #include diff --git a/libfprint/drivers/goodixtls/goodix_proto.h b/libfprint/drivers/goodixtls/goodix_proto.h index 8f55c8eb9..709b35959 100644 --- a/libfprint/drivers/goodixtls/goodix_proto.h +++ b/libfprint/drivers/goodixtls/goodix_proto.h @@ -1,22 +1,23 @@ -// Goodix Tls driver for libfprint - -// Copyright (C) 2021 Alexander Meiler -// Copyright (C) 2021 Matthieu CHARETTE -// Copyright (C) 2021 Natasha England-Elbro - -// 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 +/* Goodix TLS driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * Copyright (C) 2021 Natasha England-Elbro + * + * 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 + */ #pragma once diff --git a/libfprint/drivers/goodixtls/goodixtls.c b/libfprint/drivers/goodixtls/goodixtls.c index 8ab6d1c70..24c3ad4b4 100644 --- a/libfprint/drivers/goodixtls/goodixtls.c +++ b/libfprint/drivers/goodixtls/goodixtls.c @@ -1,22 +1,23 @@ -// Goodix Tls driver for libfprint - -// Copyright (C) 2021 Alexander Meiler -// Copyright (C) 2021 Matthieu CHARETTE -// Copyright (C) 2021 Natasha England-Elbro - -// 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 +/* Goodix TLS driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * Copyright (C) 2021 Natasha England-Elbro + * + * 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 + */ #include #include @@ -100,9 +101,7 @@ tls_server_psk_server_callback (SSL *ssl, return 0; } - // zero out the psk - for (int n = 0; n != len; ++n) - psk[n] = 0; + memset (psk, 0, len); return len; } diff --git a/libfprint/drivers/goodixtls/goodixtls.h b/libfprint/drivers/goodixtls/goodixtls.h index d5ffe1375..716769a81 100644 --- a/libfprint/drivers/goodixtls/goodixtls.h +++ b/libfprint/drivers/goodixtls/goodixtls.h @@ -1,22 +1,23 @@ -// Goodix Tls driver for libfprint - -// Copyright (C) 2021 Alexander Meiler -// Copyright (C) 2021 Matthieu CHARETTE -// Copyright (C) 2021 Natasha England-Elbro - -// 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 +/* Goodix TLS driver for libfprint + * + * Copyright (C) 2021 Alexander Meiler + * Copyright (C) 2021 Matthieu CHARETTE + * Copyright (C) 2021 Natasha England-Elbro + * + * 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 + */ #pragma once @@ -37,7 +38,7 @@ struct _GoodixTlsServer; */ typedef struct _GoodixTlsServer { - gpointer user_data; // Passed to all callbacks + gpointer user_data; /* Passed to all callbacks */ SSL_CTX *ssl_ctx; SSL *ssl_layer; From 078516ba0623addca9dbb083ddb82eb96cbc35e4 Mon Sep 17 00:00:00 2001 From: Nix User Date: Mon, 7 Sep 2026 02:06:40 +0530 Subject: [PATCH 07/17] goodixtls: port session parking, warm activation, best-of-N capture, and conditional reset --- libfprint/drivers/goodixtls/goodix.c | 166 ++++++++- libfprint/drivers/goodixtls/goodix.h | 30 ++ libfprint/drivers/goodixtls/goodix5e0a.c | 446 ++++++++++++++++++++++- libfprint/drivers/goodixtls/goodix5e0a.h | 1 + 4 files changed, 613 insertions(+), 30 deletions(-) diff --git a/libfprint/drivers/goodixtls/goodix.c b/libfprint/drivers/goodixtls/goodix.c index 2a91c0f7d..58016e9fe 100644 --- a/libfprint/drivers/goodixtls/goodix.c +++ b/libfprint/drivers/goodixtls/goodix.c @@ -60,6 +60,21 @@ typedef struct * TLS completion callbacks drop on mismatch. */ guint activation_gen; + /* Boot sequence counter coupled to actual USB reset. */ + guint boot_seq; + + /* Conditional USB reset flag: TRUE only when previous session closed cleanly. + * FALSE is the safe direction (reset taken on open). */ + gboolean clean_close; + + /* USB device identity snapshot to detect kernel re-enumeration. */ + guint8 last_usb_bus; + guint8 last_usb_addr; + guint8 last_usb_port; + guint16 last_usb_vid; + guint16 last_usb_pid; + gboolean usb_identity_valid; + GCancellable *transfer_cancel_tkn; gboolean inited; } FpiDeviceGoodixTlsPrivate; @@ -1282,10 +1297,69 @@ goodix_dev_init (FpDevice *dev, GError **error) priv->length = 0; priv->transfer_cancel_tkn = g_cancellable_new (); - g_usb_device_reset (fpi_device_get_usb_device (dev), NULL); - - return g_usb_device_claim_interface (fpi_device_get_usb_device (dev), - class->interface, 0, error); + /* Conditional USB reset: skip reset only when previous session on + * this USB device closed cleanly. Re-enumeration or dirty state forces reset. */ + { + GUsbDevice *usb = fpi_device_get_usb_device (dev); + gboolean reenumerated = FALSE; + gboolean take_reset; + + if (usb != NULL) + { + guint8 bus = g_usb_device_get_bus (usb); + guint8 addr = g_usb_device_get_address (usb); + guint8 port = g_usb_device_get_port_number (usb); + guint16 vid = g_usb_device_get_vid (usb); + guint16 pid = g_usb_device_get_pid (usb); + + if (priv->usb_identity_valid + && (bus != priv->last_usb_bus || addr != priv->last_usb_addr + || port != priv->last_usb_port || vid != priv->last_usb_vid + || pid != priv->last_usb_pid)) + { + reenumerated = TRUE; + priv->clean_close = FALSE; + } + priv->last_usb_bus = bus; + priv->last_usb_addr = addr; + priv->last_usb_port = port; + priv->last_usb_vid = vid; + priv->last_usb_pid = pid; + priv->usb_identity_valid = TRUE; + } + else + { + reenumerated = TRUE; + priv->clean_close = FALSE; + } + + take_reset = !priv->clean_close; + if (take_reset) + { + priv->boot_seq++; + if (reenumerated) + g_message ("5e0a USB reset taken (re-enumerated device, boot_seq=%u)", + priv->boot_seq); + else + g_message ("5e0a USB reset taken (dirty close, boot_seq=%u)", + priv->boot_seq); + g_usb_device_reset (fpi_device_get_usb_device (dev), NULL); + } + else + { + g_message ("5e0a USB reset skipped (clean close, boot_seq=%u)", + priv->boot_seq); + } + } + + { + gboolean ok = g_usb_device_claim_interface (fpi_device_get_usb_device (dev), + class->interface, 0, error); + + if (!ok) + priv->clean_close = FALSE; + return ok; + } } void goodix_reset_state (FpDevice *dev) @@ -1326,6 +1400,46 @@ goodix_activation_gen_bump (FpDevice *dev) return ++priv->activation_gen; } +guint +goodix_boot_seq_get (FpDevice *dev) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + return priv->boot_seq; +} + +void +goodix_session_mark_clean (FpDevice *dev) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + priv->clean_close = TRUE; +} + +void +goodix_session_mark_dirty (FpDevice *dev) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + priv->clean_close = FALSE; +} + +gboolean +goodix_session_is_clean (FpDevice *dev) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + return priv->clean_close; +} + gboolean goodix_dev_deinit (FpDevice *dev, GError **error) { @@ -1333,23 +1447,40 @@ goodix_dev_deinit (FpDevice *dev, GError **error) FpiDeviceGoodixTlsClass *class = FPI_DEVICE_GOODIXTLS_GET_CLASS (self); FpiDeviceGoodixTlsPrivate *priv = fpi_device_goodixtls_get_instance_private (self); + gboolean clean_close = priv->clean_close; + gboolean released; - /* Teardown entry: orphan any in-flight TLS activation. */ - goodix_activation_gen_bump (dev); + if (!clean_close) + goodix_activation_gen_bump (dev); g_cancellable_cancel (priv->transfer_cancel_tkn); g_clear_object (&priv->transfer_cancel_tkn); - g_autoptr(GError) tls_err = NULL; - goodix_shutdown_tls (dev, &tls_err); - if (tls_err) - fp_warn ("TLS shutdown warning: %s", tls_err->message); + if (!clean_close) + { + g_autoptr(GError) tls_err = NULL; + + goodix_shutdown_tls (dev, &tls_err); + if (tls_err) + fp_warn ("TLS shutdown warning: %s", tls_err->message); + } goodix_reset_state (dev); priv->inited = FALSE; - return g_usb_device_release_interface (fpi_device_get_usb_device (dev), - class->interface, 0, error); + released = g_usb_device_release_interface (fpi_device_get_usb_device (dev), + class->interface, 0, error); + if (!released) + { + priv->clean_close = FALSE; + if (clean_close) + { + goodix_activation_gen_bump (dev); + goodix_shutdown_tls (dev, NULL); + } + } + + return released; } void @@ -1668,6 +1799,17 @@ goodix_shutdown_tls (FpDevice *dev, GError **error) } return TRUE; } + +gboolean +goodix_tls_is_alive (FpDevice *dev) +{ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + + return priv->tls_hop != NULL; +} + static void goodix_tls_ready_image_handler (FpDevice *dev, guint8 *data, guint16 length, gpointer user_data, diff --git a/libfprint/drivers/goodixtls/goodix.h b/libfprint/drivers/goodixtls/goodix.h index 10a2f3c4a..ad8a6cab4 100644 --- a/libfprint/drivers/goodixtls/goodix.h +++ b/libfprint/drivers/goodixtls/goodix.h @@ -542,6 +542,28 @@ void goodix_reset_state (FpDevice *dev); guint goodix_activation_gen_get (FpDevice *dev); guint goodix_activation_gen_bump (FpDevice *dev); +/** + * @brief Warm-activation boot sequence counter. + * + * Bumped in goodix_dev_init when a USB reset is actually performed. + * + * @param dev + * @return current boot sequence number + */ +guint goodix_boot_seq_get (FpDevice *dev); + +/** + * @brief Clean-vs-dirty session lifetime tracking for conditional USB reset. + * + * Mark clean only when a session completes deactivate cleanly with a live + * TLS session; mark dirty on any error or full teardown. + * + * @param dev + */ +void goodix_session_mark_clean (FpDevice *dev); +void goodix_session_mark_dirty (FpDevice *dev); +gboolean goodix_session_is_clean (FpDevice *dev); + /** * @brief Read a TLS packet from the device * @note You probably won't ever need to call this directly from your driver @@ -575,6 +597,14 @@ void goodix_tls_init (FpDevice *dev, gboolean goodix_shutdown_tls (FpDevice *dev, GError **error); +/** + * @brief Check whether a negotiated TLS session context is currently alive. + * + * @param dev + * @return TRUE when priv->tls_hop != NULL, FALSE otherwise + */ +gboolean goodix_tls_is_alive (FpDevice *dev); + /** * @brief Read a TLS encrypted image from the device and decrypt it * diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c index 708473bf7..4417fae55 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.c +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -54,6 +54,25 @@ struct _FpiDeviceGoodixTls5e0a gboolean session_started; FpiSsm *scan_ssm; GSource *down_timeout; + + /* TLS session parking state across deactivate/activate cycles */ + gboolean tls_parked; + gint64 tls_parked_at; + guint tls_parked_gen; + + /* Warm activation state to avoid redundant sensor reset/configuration */ + gboolean warm_ok; + gint64 last_clean_mono; + guint warm_boot_seq; + const char *warm_down_reason; + gboolean warm_attempted; + gboolean warm_retried; + + /* Multi-frame burst capture and best-frame selection */ + guint frame_count; + FpImage *best_img; + guint best_minutiae; + guint best_frame_no; }; G_DECLARE_FINAL_TYPE (FpiDeviceGoodixTls5e0a, fpi_device_goodixtls5e0a, FPI, @@ -62,6 +81,12 @@ G_DECLARE_FINAL_TYPE (FpiDeviceGoodixTls5e0a, fpi_device_goodixtls5e0a, FPI, G_DEFINE_TYPE (FpiDeviceGoodixTls5e0a, fpi_device_goodixtls5e0a, FPI_TYPE_DEVICE_GOODIXTLS5XX); +static void goodix5e0a_reset_touch_frames (FpiDeviceGoodixTls5e0a *self); + +#define GOODIX_5E0A_TLS_PARK_TTL_US (G_USEC_PER_SEC * 30) +#define GOODIX_5E0A_TLS_PARK_HEALTH_TIMEOUT_MS 500 +#define GOODIX_5E0A_WARM_TTL_US (G_USEC_PER_SEC * 60) + enum activate_states { ACTIVATE_READ_AND_NOP, ACTIVATE_RESET, @@ -72,11 +97,13 @@ enum activate_states { ACTIVATE_NUM_STATES, }; -/* No PSK reconciliation: activation goes CHECK_FW_VER -> UPLOAD_CONFIG -> TLS with the static host key (0xe4 slot reports factory bytes, 0xe0 writes rejected). */ +static void activate_complete (FpiSsm *ssm, FpDevice *dev, GError *error); static void activate_run_state (FpiSsm *ssm, FpDevice *dev) { + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + switch (fpi_ssm_get_cur_state (ssm)) { case ACTIVATE_READ_AND_NOP: @@ -85,14 +112,29 @@ activate_run_state (FpiSsm *ssm, FpDevice *dev) break; case ACTIVATE_RESET: + if (self->warm_attempted) + { + fpi_ssm_jump_to_state (ssm, ACTIVATE_CHECK_FW_VER); + return; + } goodix_send_reset (dev, TRUE, 20, goodixtls5xx_check_reset, ssm); break; case ACTIVATE_READ_CHIP_ID: + if (self->warm_attempted) + { + fpi_ssm_jump_to_state (ssm, ACTIVATE_CHECK_FW_VER); + return; + } goodix_send_read_sensor_register (dev, 0x0000, 4, goodixtls5xx_check_none_cmd, ssm); break; case ACTIVATE_READ_OTP: + if (self->warm_attempted) + { + fpi_ssm_jump_to_state (ssm, ACTIVATE_CHECK_FW_VER); + return; + } goodix_send_read_otp (dev, goodixtls5xx_check_none_cmd, ssm); break; @@ -101,6 +143,11 @@ activate_run_state (FpiSsm *ssm, FpDevice *dev) break; case ACTIVATE_UPLOAD_CONFIG: + if (self->warm_attempted) + { + fpi_ssm_jump_to_state (ssm, ACTIVATE_NUM_STATES); + return; + } goodix_send_upload_config_mcu (dev, (guint8 *) goodix_5e0a_config, sizeof (goodix_5e0a_config), NULL, goodixtls5xx_check_config_upload, ssm); @@ -111,19 +158,130 @@ activate_run_state (FpiSsm *ssm, FpDevice *dev) static void on_chip_enabled (FpDevice *dev, gpointer user_data, GError *error) { + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + if (error) { + self->warm_ok = FALSE; + self->warm_down_reason = "failed-last"; + self->warm_attempted = FALSE; + goodix_session_mark_dirty (dev); fp_err ("failed to enable chip: %s (code: %d)", error->message, error->code); fpi_image_device_activate_complete (FP_IMAGE_DEVICE (dev), error); return; } + self->warm_ok = TRUE; + self->last_clean_mono = g_get_monotonic_time (); + self->warm_boot_seq = goodix_boot_seq_get (dev); + self->warm_attempted = FALSE; fp_dbg ("Chip enabled! Activation complete."); fpi_image_device_activate_complete (FP_IMAGE_DEVICE (dev), NULL); } +static gboolean +goodix5e0a_warm_fresh (FpDevice *dev) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + + return self->warm_ok + && self->warm_boot_seq == goodix_boot_seq_get (dev) + && (g_get_monotonic_time () - self->last_clean_mono) < GOODIX_5E0A_WARM_TTL_US; +} + +static void +goodix5e0a_log_warm_taken (FpDevice *dev) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + + g_message ("5e0a warm activation: reusing MCU config (age=%.1fs, boot_seq=%u)", + (g_get_monotonic_time () - self->last_clean_mono) / (gdouble) G_USEC_PER_SEC, + self->warm_boot_seq); +} + +static void +goodix5e0a_start_warm_activation (FpDevice *dev) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + + self->session_started = FALSE; + self->scan_ssm = NULL; + self->down_timeout = NULL; + self->warm_attempted = TRUE; + + g_message ("5e0a warm path: skipping RESET + config upload, entry=CHECK_FW_VER"); + fpi_ssm_start (fpi_ssm_new (dev, activate_run_state, ACTIVATE_NUM_STATES), + activate_complete); +} + +static void +goodix5e0a_start_full_activation (FpDevice *dev) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + + self->session_started = FALSE; + self->scan_ssm = NULL; + self->down_timeout = NULL; + self->warm_attempted = FALSE; + + fpi_ssm_start (fpi_ssm_new (dev, activate_run_state, ACTIVATE_NUM_STATES), + activate_complete); +} + +static void +on_parked_health_reply (FpDevice *dev, gpointer user_data, GError *error) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + guint gen = GPOINTER_TO_UINT (user_data); + + if (gen != goodix_activation_gen_get (dev)) + { + fp_dbg ("dropping stale parked-TLS health reply"); + if (error) + g_error_free (error); + return; + } + + if (error) + { + const char *reason = "tls-error"; + gboolean transport_miss = FALSE; + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) + { + reason = "timeout"; + transport_miss = TRUE; + } + g_error_free (error); + goodix_shutdown_tls (dev, NULL); + goodix_reset_state (dev); + if (transport_miss) + { + self->warm_ok = FALSE; + self->warm_down_reason = "transport-miss"; + } + else if (goodix5e0a_warm_fresh (dev)) + { + goodix5e0a_log_warm_taken (dev); + goodix5e0a_start_warm_activation (dev); + return; + } + g_message ("5e0a parked TLS session unhealthy (%s), full re-handshake", reason); + goodix5e0a_start_full_activation (dev); + return; + } + + g_message ("5e0a TLS session reused (parked %.1fs, gen=%u)", + (g_get_monotonic_time () - self->tls_parked_at) / (gdouble) G_USEC_PER_SEC, + gen); + fp_dbg ("parked TLS session healthy, confirming chip enable"); + goodix_send_enable_chip (dev, TRUE, on_chip_enabled, NULL); +} + static void on_tls_activation_complete (FpDevice *dev, gpointer user_data, GError *error) { + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + /* Drop this completion if a deactivate bumped the generation while the TLS handshake was in flight. */ if (GPOINTER_TO_UINT (user_data) != goodix_activation_gen_get (dev)) { @@ -135,6 +293,23 @@ on_tls_activation_complete (FpDevice *dev, gpointer user_data, GError *error) if (error) { + goodix_session_mark_dirty (dev); + if (self->warm_attempted && !self->warm_retried) + { + self->warm_ok = FALSE; + self->warm_down_reason = "failed-last"; + self->warm_attempted = FALSE; + self->warm_retried = TRUE; + g_message ("5e0a warm attempt failed (%s), retrying full ladder", error->message); + g_error_free (error); + goodix_shutdown_tls (dev, NULL); + goodix_reset_state (dev); + goodix5e0a_start_full_activation (dev); + return; + } + self->warm_ok = FALSE; + self->warm_down_reason = "failed-last"; + self->warm_attempted = FALSE; fp_err ("failed during TLS activation: %s (code: %d)", error->message, error->code); fpi_image_device_activate_complete (FP_IMAGE_DEVICE (dev), error); return; @@ -147,6 +322,8 @@ on_tls_activation_complete (FpDevice *dev, gpointer user_data, GError *error) static void activate_complete (FpiSsm *ssm, FpDevice *dev, GError *error) { + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + G_DEBUG_HERE (); if (!error) { @@ -156,6 +333,21 @@ activate_complete (FpiSsm *ssm, FpDevice *dev, GError *error) } else { + goodix_session_mark_dirty (dev); + if (self->warm_attempted && !self->warm_retried) + { + self->warm_ok = FALSE; + self->warm_down_reason = "failed-last"; + self->warm_attempted = FALSE; + self->warm_retried = TRUE; + g_message ("5e0a warm attempt failed (%s), retrying full ladder", error->message); + g_error_free (error); + goodix5e0a_start_full_activation (dev); + return; + } + self->warm_ok = FALSE; + self->warm_down_reason = "failed-last"; + self->warm_attempted = FALSE; fp_err ("failed during activation: %s (code: %d)", error->message, error->code); fpi_image_device_activate_complete (FP_IMAGE_DEVICE (dev), error); } @@ -167,15 +359,82 @@ dev_activate (FpImageDevice *img_dev) FpDevice *dev = FP_DEVICE (img_dev); FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); - /* Invalidate any in-flight activation from a previous session. */ - goodix_activation_gen_bump (dev); + guint pre_gen = goodix_activation_gen_get (dev); + guint new_gen = goodix_activation_gen_bump (dev); - self->session_started = FALSE; - self->scan_ssm = NULL; - self->down_timeout = NULL; + goodix5e0a_reset_touch_frames (self); + self->warm_retried = FALSE; - fpi_ssm_start (fpi_ssm_new (dev, activate_run_state, ACTIVATE_NUM_STATES), - activate_complete); + if (self->tls_parked && self->tls_parked_gen == pre_gen + && goodix_tls_is_alive (dev) + && (g_get_monotonic_time () - self->tls_parked_at) < GOODIX_5E0A_TLS_PARK_TTL_US) + { + GoodixCallbackInfo *cb_info; + GoodixQueryMcuState payload; + + self->tls_parked = FALSE; + self->scan_ssm = NULL; + self->down_timeout = NULL; + fp_dbg ("5e0a parked TLS session candidate fresh, health-checking (gen=%u)", new_gen); + goodix_start_read_loop (dev); + + cb_info = g_new0 (GoodixCallbackInfo, 1); + cb_info->callback = G_CALLBACK (on_parked_health_reply); + cb_info->user_data = GUINT_TO_POINTER (new_gen); + payload.unused_flags = 0x55; + goodix_send_protocol (dev, GOODIX_CMD_QUERY_MCU_STATE, + (guint8 *) &payload, sizeof (payload), + NULL, TRUE, + GOODIX_5E0A_TLS_PARK_HEALTH_TIMEOUT_MS, + FALSE, goodix_receive_none, cb_info); + return; + } + + if (self->tls_parked) + { + const char *reason; + + if (self->tls_parked_gen != pre_gen) + reason = "gen-mismatch"; + else if (!goodix_tls_is_alive (dev)) + reason = "tls-error"; + else + reason = "expired"; + self->tls_parked = FALSE; + g_message ("5e0a parked TLS session unhealthy (%s), full re-handshake", reason); + goodix_shutdown_tls (dev, NULL); + } + + if (goodix5e0a_warm_fresh (dev)) + { + goodix5e0a_log_warm_taken (dev); + goodix5e0a_start_warm_activation (dev); + return; + } + + { + const char *reason; + + if (self->warm_ok && self->warm_boot_seq == goodix_boot_seq_get (dev)) + { + reason = "ttl-expired"; + self->warm_ok = FALSE; + self->warm_down_reason = "ttl-expired"; + } + else if (self->warm_ok) + { + reason = "cold-start"; + self->warm_ok = FALSE; + self->warm_down_reason = "cold-start"; + } + else + { + reason = self->warm_down_reason ? self->warm_down_reason : "cold-start"; + } + self->warm_attempted = FALSE; + g_message ("5e0a warm expired: reason=%s", reason); + goodix5e0a_start_full_activation (dev); + } } enum goodix5e0a_scan_states { @@ -198,7 +457,7 @@ send_cmd_noreply (FpDevice *dev, guint8 cmd, const guint8 *payload, guint16 len, if (cb) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (cb); cb_info->user_data = user_data; callback = goodix_receive_none; @@ -217,7 +476,7 @@ send_cmd_reply (FpDevice *dev, guint8 cmd, const guint8 *payload, guint16 len, if (cb) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (cb); cb_info->user_data = user_data; callback = goodix_receive_default; @@ -376,18 +635,80 @@ goodix5e0a_decode_frame (GoodixTls5xxPix *out_row_major, const guint8 *data, gui return pixel_idx; } +static void +goodix5e0a_reset_touch_frames (FpiDeviceGoodixTls5e0a *self) +{ + if (self->best_img != NULL) + { + g_object_unref (self->best_img); + self->best_img = NULL; + } + self->frame_count = 0; + self->best_minutiae = 0; + self->best_frame_no = 0; +} + +static FpImage * +goodix5e0a_claim_best_frame (FpiDeviceGoodixTls5e0a *self) +{ + FpImage *best; + + g_return_val_if_fail (self->best_img != NULL, NULL); + best = self->best_img; + g_message ("5e0a best frame %u/%u: minutiae=%u score-proxy=%u (submitting)", + self->best_frame_no, (guint) GOODIX_5E0A_FRAMES_PER_TOUCH, + self->best_minutiae, self->best_minutiae); + self->best_img = NULL; + self->best_minutiae = 0; + self->best_frame_no = 0; + return best; +} + +static gboolean +goodix5e0a_keep_best_frame (FpDevice *dev, gpointer ssm, FpImage *img, + guint16 declen, guint active, guint range); + static void goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, gpointer ssm, GError *err) { FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + FpiDeviceAction action = fpi_device_get_current_action (dev); + g_autofree GoodixTls5xxPix *raw_frame = NULL; + FpImage *img; + if (self->scan_ssm != ssm) + { + if (err) + g_error_free (err); + return; + } + + /* Fall back to the best frame captured so far if a mid-burst read fails. */ if (err) { + if (action != FPI_DEVICE_ACTION_ENROLL && self->best_img != NULL) + { + g_error_free (err); + img = goodix5e0a_claim_best_frame (self); + goto deliver; + } fpi_ssm_mark_failed (ssm, err); return; } + /* Submit the best frame captured so far if a mid-burst read is truncated. */ + if (action != FPI_DEVICE_ACTION_ENROLL && self->best_img != NULL + && (data == NULL || len < GOODIX_5E0A_FRAME_WIRE_BYTES)) + { + g_message ("5e0a frame %u/%u: short declen=%u, submitting best-so-far %u/%u", + self->frame_count + 1, (guint) GOODIX_5E0A_FRAMES_PER_TOUCH, + len, self->best_frame_no, + (guint) GOODIX_5E0A_FRAMES_PER_TOUCH); + img = goodix5e0a_claim_best_frame (self); + goto deliver; + } + fp_dbg ("5e0a scan_on_read_img: declen=%u", len); if (data && len >= 16) @@ -410,11 +731,13 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, } } - GoodixTls5xxPix *raw_frame = calloc (GOODIX_5E0A_FRAME_SIZE, sizeof (GoodixTls5xxPix)); + raw_frame = g_new0 (GoodixTls5xxPix, GOODIX_5E0A_FRAME_SIZE); guint32 decoded_pixels = goodix5e0a_decode_frame (raw_frame, data, len); guint total_nonzero = 0; guint16 raw_min = 65535, raw_max = 0; + guint frame_active = 0; + guint16 frame_min = 65535, frame_max = 0; for (guint32 i = 0; i < GOODIX_5E0A_FRAME_SIZE; i++) { if (raw_frame[i] > 0) @@ -425,7 +748,17 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, if (raw_frame[i] > raw_max) raw_max = raw_frame[i]; } + if (raw_frame[i] > 30) + { + frame_active++; + if (raw_frame[i] < frame_min) + frame_min = raw_frame[i]; + if (raw_frame[i] > frame_max) + frame_max = raw_frame[i]; + } } + guint frame_range = (frame_min != 65535 && frame_max > frame_min) + ? (guint) (frame_max - frame_min) : 0; fp_dbg ("5e0a wire layout: decoded_px=%u blocks=%u active_bytes=%u padding_nonzero=%u footer_bytes=%u", decoded_pixels, MIN ((guint32) len / GOODIX_5E0A_BLOCK_BYTES, (guint32) GOODIX_5E0A_FRAME_BLOCKS), @@ -435,8 +768,7 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, decoded_pixels, total_nonzero, raw_min == 65535 ? 0 : raw_min, raw_max, GOODIX_5E0A_WIDTH, GOODIX_5E0A_HEIGHT); - FpImage *img = process_raw_frame (raw_frame); - free (raw_frame); + img = process_raw_frame (raw_frame); if (img == NULL) { @@ -445,7 +777,6 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, img->ppmm = 500.0 / 25.4; } - FpiDeviceAction action = fpi_device_get_current_action (dev); if (action == FPI_DEVICE_ACTION_ENROLL) { guint minutiae_count = goodix5e0a_count_minutiae (img); @@ -462,7 +793,14 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, } } - /* Verify passthrough: report capture immediately so auth finishes without waiting for finger-lift polls. */ + if (action != FPI_DEVICE_ACTION_ENROLL) + { + if (goodix5e0a_keep_best_frame (dev, ssm, img, len, frame_active, frame_range)) + return; + img = goodix5e0a_claim_best_frame (self); + } + +deliver: fpi_image_device_image_captured (FP_IMAGE_DEVICE (dev), img); if (action != FPI_DEVICE_ACTION_ENROLL) @@ -477,6 +815,39 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, } } +static gboolean +goodix5e0a_keep_best_frame (FpDevice *dev, gpointer ssm, FpImage *img, + guint16 declen, guint active, guint range) +{ + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + guint minutiae = goodix5e0a_count_minutiae (img); + + self->frame_count++; + g_message ("5e0a frame %u/%u: declen=%u active=%u range=%u minutiae=%u score-proxy=%u", + self->frame_count, (guint) GOODIX_5E0A_FRAMES_PER_TOUCH, + declen, active, range, minutiae, minutiae); + + if (self->best_img == NULL || minutiae > self->best_minutiae) + { + if (self->best_img != NULL) + g_object_unref (self->best_img); + self->best_img = img; + self->best_minutiae = minutiae; + self->best_frame_no = self->frame_count; + } + else + { + g_object_unref (img); + } + + if (self->frame_count < GOODIX_5E0A_FRAMES_PER_TOUCH) + { + goodix_tls_read_image (dev, goodix5e0a_on_read_img, ssm); + return TRUE; + } + return FALSE; +} + static void goodix5e0a_on_fdt_up_reply (FpDevice *dev, guint8 *data, guint16 len, gpointer ssm, GError *err) @@ -561,6 +932,7 @@ goodix5e0a_scan_complete (FpiSsm *ssm, FpDevice *dev, GError *error) FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); self->scan_ssm = NULL; + goodix5e0a_reset_touch_frames (self); if (self->down_timeout) { g_source_destroy (self->down_timeout); @@ -569,6 +941,8 @@ goodix5e0a_scan_complete (FpiSsm *ssm, FpDevice *dev, GError *error) if (error) { + goodix_session_mark_dirty (dev); + self->warm_ok = FALSE; fp_err ("5e0a failed to scan: %s (code: %d)", error->message, error->code); fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error); return; @@ -587,6 +961,8 @@ goodix5e0a_scan_start (FpDevice *dev) return; } + goodix5e0a_reset_touch_frames (self); + self->scan_ssm = fpi_ssm_new (dev, goodix5e0a_scan_run_state, SCAN_5E0A_NUM_STATES); fpi_ssm_start (self->scan_ssm, goodix5e0a_scan_complete); } @@ -604,7 +980,7 @@ goodix5e0a_deactivate (FpImageDevice *img_dev) FpDevice *dev = FP_DEVICE (img_dev); FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); - /* Orphan any in-flight TLS activation; its completion will drop. */ + goodix5e0a_reset_touch_frames (self); goodix_activation_gen_bump (dev); self->session_started = FALSE; @@ -621,10 +997,24 @@ goodix5e0a_deactivate (FpImageDevice *img_dev) self->scan_ssm = NULL; } - GError *tls_err = NULL; + if (goodix_tls_is_alive (dev) && self->warm_ok) + { + goodix_stop_read_loop (dev); + self->tls_parked = TRUE; + self->tls_parked_at = g_get_monotonic_time (); + self->tls_parked_gen = goodix_activation_gen_get (dev); + goodix_session_mark_clean (dev); + fp_dbg ("5e0a parking live TLS session (gen=%u)", self->tls_parked_gen); + fpi_image_device_deactivate_complete (img_dev, NULL); + return; + } + + self->tls_parked = FALSE; + goodix_session_mark_dirty (dev); + g_autoptr(GError) tls_err = NULL; goodix_shutdown_tls (dev, &tls_err); goodix_stop_read_loop (dev); - fpi_image_device_deactivate_complete (img_dev, tls_err); + fpi_image_device_deactivate_complete (img_dev, g_steal_pointer (&tls_err)); } static void @@ -633,6 +1023,19 @@ fpi_device_goodixtls5e0a_init (FpiDeviceGoodixTls5e0a *self) self->session_started = FALSE; self->scan_ssm = NULL; self->down_timeout = NULL; + self->tls_parked = FALSE; + self->tls_parked_at = 0; + self->tls_parked_gen = 0; + self->warm_ok = FALSE; + self->last_clean_mono = 0; + self->warm_boot_seq = 0; + self->warm_down_reason = "cold-start"; + self->warm_attempted = FALSE; + self->warm_retried = FALSE; + self->frame_count = 0; + self->best_img = NULL; + self->best_minutiae = 0; + self->best_frame_no = 0; } static double @@ -866,6 +1269,13 @@ goodix5e0a_suspend (FpDevice *dev) /* Orphan any in-flight TLS handshake/activation; its completion will drop. */ goodix_activation_gen_bump (dev); + self->tls_parked = FALSE; + goodix_session_mark_dirty (dev); + self->warm_ok = FALSE; + self->warm_down_reason = "suspended"; + self->warm_attempted = FALSE; + goodix5e0a_reset_touch_frames (self); + self->session_started = FALSE; if (self->down_timeout) { diff --git a/libfprint/drivers/goodixtls/goodix5e0a.h b/libfprint/drivers/goodixtls/goodix5e0a.h index 44d51d2b1..33857b5c1 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.h +++ b/libfprint/drivers/goodixtls/goodix5e0a.h @@ -43,6 +43,7 @@ #define GOODIX_5E0A_CONTRAST_GAIN (1.0f) #define GOODIX_5E0A_ENROLL_MIN_MINUTIAE (12) +#define GOODIX_5E0A_FRAMES_PER_TOUCH 3 /* Sensor Analog Front-End (AFE) Gain/Exposure Register Configuration */ From 90d510fd131aca2f7dc288e10da7fc5e7ac4452b Mon Sep 17 00:00:00 2001 From: Nix User Date: Mon, 7 Sep 2026 03:00:33 +0530 Subject: [PATCH 08/17] drivers/goodixtls: set bz3_threshold to 10 --- libfprint/drivers/goodixtls/goodix5e0a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c index 4417fae55..cef303024 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.c +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -1349,7 +1349,7 @@ fpi_device_goodixtls5e0a_class_init (FpiDeviceGoodixTls5e0aClass * class) img_dev_class->activate = dev_activate; img_dev_class->change_state = goodix5e0a_change_state; img_dev_class->deactivate = goodix5e0a_deactivate; - img_dev_class->bz3_threshold = 12; + img_dev_class->bz3_threshold = 10; img_dev_class->img_width = GOODIX_5E0A_SCALED_WIDTH; img_dev_class->img_height = GOODIX_5E0A_SCALED_HEIGHT; From 2cdcd4e683b41a7265210269e817b1fc8b57f582 Mon Sep 17 00:00:00 2001 From: Nix User Date: Mon, 7 Sep 2026 03:14:42 +0530 Subject: [PATCH 09/17] drivers/goodixtls: calibrate bz3_threshold to 11 --- libfprint/drivers/goodixtls/goodix5e0a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c index cef303024..4b287b8a2 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.c +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -1349,7 +1349,7 @@ fpi_device_goodixtls5e0a_class_init (FpiDeviceGoodixTls5e0aClass * class) img_dev_class->activate = dev_activate; img_dev_class->change_state = goodix5e0a_change_state; img_dev_class->deactivate = goodix5e0a_deactivate; - img_dev_class->bz3_threshold = 10; + img_dev_class->bz3_threshold = 11; img_dev_class->img_width = GOODIX_5E0A_SCALED_WIDTH; img_dev_class->img_height = GOODIX_5E0A_SCALED_HEIGHT; From a5029fea1860265ae4e22c2d286ea48e3c8f62d6 Mon Sep 17 00:00:00 2001 From: Nix User Date: Wed, 9 Sep 2026 02:49:10 +0530 Subject: [PATCH 10/17] goodixtls: bypass cold AFE reset, add retry guard, harden frame decoder and TLS --- libfprint/drivers/goodixtls/goodix5e0a.c | 138 ++++++++++++++++------- libfprint/drivers/goodixtls/goodix5xx.c | 46 +++++--- libfprint/drivers/goodixtls/goodix5xx.h | 2 + libfprint/drivers/goodixtls/goodixtls.c | 42 ++++--- 4 files changed, 161 insertions(+), 67 deletions(-) diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c index 4b287b8a2..e6208c6c7 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.c +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -53,6 +53,8 @@ struct _FpiDeviceGoodixTls5e0a gboolean session_started; FpiSsm *scan_ssm; + guint scan_gen; + guint scan_timeout_gen; GSource *down_timeout; /* TLS session parking state across deactivate/activate cycles */ @@ -73,6 +75,10 @@ struct _FpiDeviceGoodixTls5e0a FpImage *best_img; guint best_minutiae; guint best_frame_no; + + /* Verify retry guard against rapid retry burn on continuous touch */ + gboolean retry_guard; + gint64 retry_guard_mono; }; G_DECLARE_FINAL_TYPE (FpiDeviceGoodixTls5e0a, fpi_device_goodixtls5e0a, FPI, @@ -112,12 +118,11 @@ activate_run_state (FpiSsm *ssm, FpDevice *dev) break; case ACTIVATE_RESET: - if (self->warm_attempted) - { - fpi_ssm_jump_to_state (ssm, ACTIVATE_CHECK_FW_VER); - return; - } - goodix_send_reset (dev, TRUE, 20, goodixtls5xx_check_reset, ssm); + /* In Windows driver captures, sensor AFE reset (CMD 0xa2) is never sent + * on activation. Sending CMD 0xa2 on cold boot desynchronizes the MCU + * crypto state prior to TLS connection request (0xd0), causing bad record + * mac errors during TLS accept. Skip directly to firmware check. */ + fpi_ssm_jump_to_state (ssm, ACTIVATE_CHECK_FW_VER); break; case ACTIVATE_READ_CHIP_ID: @@ -514,7 +519,10 @@ goodix5e0a_on_d6_reply (FpDevice *dev, guint8 *data, guint16 len, } FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); self->session_started = TRUE; - fpi_ssm_next_state (ssm); + if (self->retry_guard) + fpi_ssm_jump_to_state (ssm, SCAN_5E0A_FDT_UP_1); + else + fpi_ssm_next_state (ssm); } static void goodix5e0a_on_fdt_down_reply (FpDevice *dev, @@ -533,6 +541,8 @@ goodix5e0a_on_down_poll_timeout (FpDevice *dev, gpointer user_data) FpiSsm *ssm = user_data; if (self->scan_ssm != ssm) return; + if (self->scan_timeout_gen != self->scan_gen) + return; send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, goodix_5e0a_down_s12, sizeof (goodix_5e0a_down_s12), @@ -592,6 +602,7 @@ goodix5e0a_on_fdt_down_reply (FpDevice *dev, guint8 *data, guint16 len, g_source_destroy (self->down_timeout); self->down_timeout = NULL; } + self->scan_timeout_gen = self->scan_gen; self->down_timeout = fpi_device_add_timeout (dev, 50, goodix5e0a_on_down_poll_timeout, ssm, NULL); } @@ -601,12 +612,12 @@ static guint goodix5e0a_count_minutiae (FpImage *img); static guint32 goodix5e0a_decode_frame (GoodixTls5xxPix *out_row_major, const guint8 *data, guint16 len) { - guint8 packed[GOODIX_5E0A_ACT_BYTES] = {0}; - guint32 packed_len = 0; - - if (!data) + if (!out_row_major || !data) return 0; + g_autofree guint8 *packed = g_new0 (guint8, GOODIX_5E0A_ACT_BYTES); + guint32 packed_len = 0; + /* A canonical ChicagoH frame is 80 blocks of 132 bytes followed by a * four-byte footer. Each block carries 96 packed pixel bytes and 36 zero * padding bytes. The 80 active blocks are the natural rows of a 64x80 @@ -653,7 +664,8 @@ goodix5e0a_claim_best_frame (FpiDeviceGoodixTls5e0a *self) { FpImage *best; - g_return_val_if_fail (self->best_img != NULL, NULL); + if (self->best_img == NULL) + return NULL; best = self->best_img; g_message ("5e0a best frame %u/%u: minutiae=%u score-proxy=%u (submitting)", self->best_frame_no, (guint) GOODIX_5E0A_FRAMES_PER_TOUCH, @@ -770,15 +782,15 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, img = process_raw_frame (raw_frame); - if (img == NULL) - { - img = fp_image_new (GOODIX_5E0A_SCALED_WIDTH, GOODIX_5E0A_SCALED_HEIGHT); - img->flags = FPI_IMAGE_COLORS_INVERTED; - img->ppmm = 500.0 / 25.4; - } - if (action == FPI_DEVICE_ACTION_ENROLL) { + if (img == NULL) + { + fp_dbg ("5e0a enrollment touch rejected: poor frame quality (press firmer)"); + fpi_image_device_retry_scan (FP_IMAGE_DEVICE (dev), FP_DEVICE_RETRY_TOO_SHORT); + fpi_ssm_next_state (ssm); + return; + } guint minutiae_count = goodix5e0a_count_minutiae (img); fp_dbg ("5e0a enrollment quality check: minutiae_count=%u (floor=%d)", minutiae_count, GOODIX_5E0A_ENROLL_MIN_MINUTIAE); @@ -798,16 +810,24 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, if (goodix5e0a_keep_best_frame (dev, ssm, img, len, frame_active, frame_range)) return; img = goodix5e0a_claim_best_frame (self); + if (img == NULL) + { + fpi_image_device_retry_scan (FP_IMAGE_DEVICE (dev), FP_DEVICE_RETRY_TOO_SHORT); + goto deliver_done; + } } deliver: fpi_image_device_image_captured (FP_IMAGE_DEVICE (dev), img); +deliver_done: if (action != FPI_DEVICE_ACTION_ENROLL) { self->scan_ssm = NULL; - fpi_ssm_mark_completed (ssm); + self->retry_guard = TRUE; + self->retry_guard_mono = g_get_monotonic_time (); fpi_image_device_report_finger_status (FP_IMAGE_DEVICE (dev), FALSE); + fpi_ssm_mark_completed (ssm); } else { @@ -820,24 +840,27 @@ goodix5e0a_keep_best_frame (FpDevice *dev, gpointer ssm, FpImage *img, guint16 declen, guint active, guint range) { FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); - guint minutiae = goodix5e0a_count_minutiae (img); + guint minutiae = img ? goodix5e0a_count_minutiae (img) : 0; self->frame_count++; g_message ("5e0a frame %u/%u: declen=%u active=%u range=%u minutiae=%u score-proxy=%u", self->frame_count, (guint) GOODIX_5E0A_FRAMES_PER_TOUCH, declen, active, range, minutiae, minutiae); - if (self->best_img == NULL || minutiae > self->best_minutiae) + if (img != NULL) { - if (self->best_img != NULL) - g_object_unref (self->best_img); - self->best_img = img; - self->best_minutiae = minutiae; - self->best_frame_no = self->frame_count; - } - else - { - g_object_unref (img); + if (self->best_img == NULL || minutiae > self->best_minutiae) + { + if (self->best_img != NULL) + g_object_unref (self->best_img); + self->best_img = img; + self->best_minutiae = minutiae; + self->best_frame_no = self->frame_count; + } + else + { + g_object_unref (img); + } } if (self->frame_count < GOODIX_5E0A_FRAMES_PER_TOUCH) @@ -864,6 +887,14 @@ goodix5e0a_on_fdt_up_reply (FpDevice *dev, guint8 *data, guint16 len, fp_dbg ("5e0a D34 finger release reply: len=%u", len); } + if (self->retry_guard) + { + self->retry_guard = FALSE; + fp_dbg ("5e0a retry guard: release ok, arming FDT DOWN"); + fpi_ssm_jump_to_state (ssm, SCAN_5E0A_FDT_DOWN); + return; + } + /* Mark current scan SSM completed before notifying libfprint, * so that when libfprint synchronously requests AWAIT_FINGER_ON, * the concurrency guard does not block the new scan SSM. */ @@ -888,7 +919,10 @@ goodix5e0a_scan_run_state (FpiSsm *ssm, FpDevice *dev) case SCAN_5E0A_SESSION_D6: if (self->session_started) { - fpi_ssm_jump_to_state (ssm, SCAN_5E0A_FDT_DOWN); + if (self->retry_guard) + fpi_ssm_jump_to_state (ssm, SCAN_5E0A_FDT_UP_1); + else + fpi_ssm_jump_to_state (ssm, SCAN_5E0A_FDT_DOWN); return; } send_cmd_reply (dev, GOODIX_CMD_SESSION_D6, @@ -920,8 +954,8 @@ goodix5e0a_scan_run_state (FpiSsm *ssm, FpDevice *dev) case SCAN_5E0A_FDT_UP_2: send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_UP, - goodix_5e0a_up_u01, sizeof (goodix_5e0a_up_u01), - 5000, goodix5e0a_on_fdt_up_reply, ssm); + goodix_5e0a_up_u01, sizeof (goodix_5e0a_up_u01), + self->retry_guard ? 2000 : 5000, goodix5e0a_on_fdt_up_reply, ssm); break; } } @@ -931,6 +965,7 @@ goodix5e0a_scan_complete (FpiSsm *ssm, FpDevice *dev, GError *error) { FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + self->scan_gen++; self->scan_ssm = NULL; goodix5e0a_reset_touch_frames (self); if (self->down_timeout) @@ -943,6 +978,7 @@ goodix5e0a_scan_complete (FpiSsm *ssm, FpDevice *dev, GError *error) { goodix_session_mark_dirty (dev); self->warm_ok = FALSE; + self->retry_guard = FALSE; fp_err ("5e0a failed to scan: %s (code: %d)", error->message, error->code); fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error); return; @@ -961,8 +997,23 @@ goodix5e0a_scan_start (FpDevice *dev) return; } + if (self->retry_guard) + { + gint64 delta_us = g_get_monotonic_time () - self->retry_guard_mono; + if (delta_us > 2 * G_USEC_PER_SEC) + { + fp_dbg ("5e0a retry guard expired (delta=%ld ms), clearing", (long) (delta_us / 1000)); + self->retry_guard = FALSE; + } + else + { + fp_dbg ("5e0a retry guard active (delta=%ld ms): awaiting finger release", (long) (delta_us / 1000)); + } + } + goodix5e0a_reset_touch_frames (self); + self->scan_gen++; self->scan_ssm = fpi_ssm_new (dev, goodix5e0a_scan_run_state, SCAN_5E0A_NUM_STATES); fpi_ssm_start (self->scan_ssm, goodix5e0a_scan_complete); } @@ -984,6 +1035,9 @@ goodix5e0a_deactivate (FpImageDevice *img_dev) goodix_activation_gen_bump (dev); self->session_started = FALSE; + self->scan_gen++; + self->retry_guard = FALSE; + self->retry_guard_mono = 0; if (self->down_timeout) { g_source_destroy (self->down_timeout); @@ -1022,6 +1076,8 @@ fpi_device_goodixtls5e0a_init (FpiDeviceGoodixTls5e0a *self) { self->session_started = FALSE; self->scan_ssm = NULL; + self->scan_gen = 0; + self->scan_timeout_gen = 0; self->down_timeout = NULL; self->tls_parked = FALSE; self->tls_parked_at = 0; @@ -1036,6 +1092,8 @@ fpi_device_goodixtls5e0a_init (FpiDeviceGoodixTls5e0a *self) self->best_img = NULL; self->best_minutiae = 0; self->best_frame_no = 0; + self->retry_guard = FALSE; + self->retry_guard_mono = 0; } static double @@ -1137,7 +1195,7 @@ process_raw_frame (GoodixTls5xxPix * pix) /* Remove the slowly varying pressure/offset field before global scaling. * A 3x3 local mean is the smallest window that removes this field without * averaging across a full ridge period. */ - float residual[GOODIX_5E0A_FRAME_SIZE]; + g_autofree float *residual = g_new (float, GOODIX_5E0A_FRAME_SIZE); float residual_min = G_MAXFLOAT; float residual_max = -G_MAXFLOAT; for (int y = 0; y < H; y++) @@ -1166,7 +1224,7 @@ process_raw_frame (GoodixTls5xxPix * pix) if (residual_range < 1.0f) return NULL; - guint8 normalized[GOODIX_5E0A_FRAME_SIZE]; + g_autofree guint8 *normalized = g_new (guint8, GOODIX_5E0A_FRAME_SIZE); for (guint i = 0; i < GOODIX_5E0A_FRAME_SIZE; i++) { int value = (int) roundf (128.0f + residual[i] * GOODIX_5E0A_CONTRAST_GAIN); @@ -1275,8 +1333,10 @@ goodix5e0a_suspend (FpDevice *dev) self->warm_down_reason = "suspended"; self->warm_attempted = FALSE; goodix5e0a_reset_touch_frames (self); - + self->retry_guard = FALSE; + self->retry_guard_mono = 0; self->session_started = FALSE; + self->scan_gen++; if (self->down_timeout) { g_source_destroy (self->down_timeout); @@ -1340,7 +1400,7 @@ fpi_device_goodixtls5e0a_class_init (FpiDeviceGoodixTls5e0aClass * class) dev_class->full_name = "Goodix TLS Fingerprint Sensor 5e0a"; dev_class->type = FP_DEVICE_TYPE_USB; dev_class->id_table = goodix_5e0a_id_table; - dev_class->nr_enroll_stages = 12; + dev_class->nr_enroll_stages = 5; dev_class->scan_type = FP_SCAN_TYPE_PRESS; dev_class->temp_hot_seconds = -1; /* Disable thermal watchdog */ dev_class->suspend = goodix5e0a_suspend; @@ -1349,7 +1409,7 @@ fpi_device_goodixtls5e0a_class_init (FpiDeviceGoodixTls5e0aClass * class) img_dev_class->activate = dev_activate; img_dev_class->change_state = goodix5e0a_change_state; img_dev_class->deactivate = goodix5e0a_deactivate; - img_dev_class->bz3_threshold = 11; + img_dev_class->bz3_threshold = 14; img_dev_class->img_width = GOODIX_5E0A_SCALED_WIDTH; img_dev_class->img_height = GOODIX_5E0A_SCALED_HEIGHT; diff --git a/libfprint/drivers/goodixtls/goodix5xx.c b/libfprint/drivers/goodixtls/goodix5xx.c index 4c7d3ddf8..ff7a8103f 100644 --- a/libfprint/drivers/goodixtls/goodix5xx.c +++ b/libfprint/drivers/goodixtls/goodix5xx.c @@ -86,8 +86,13 @@ on_calibrate_scan (FpDevice * dev, guint8 * data, guint16 len, gpointer ssm, GEr FpiDeviceGoodixTls5xxPrivate * priv = fpi_device_goodixtls5xx_get_instance_private (self); FpiDeviceGoodixTls5xxClass * cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (self); if (!priv->calibration_img) - priv->calibration_img = calloc (cls->scan_height * cls->scan_width, sizeof (GoodixTls5xxPix)); - goodixtls5xx_decode_frame (priv->calibration_img, len, data); + priv->calibration_img = g_try_new0 (GoodixTls5xxPix, cls->scan_height * cls->scan_width); + if (!priv->calibration_img) + { + fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + goodixtls5xx_decode_frame (priv->calibration_img, cls->scan_height * cls->scan_width, len, data); fpi_ssm_next_state (ssm); } @@ -358,8 +363,13 @@ scan_on_read_img (FpDevice *dev, guint8 *data, guint16 len, FpiDeviceGoodixTls5xxPrivate * priv = fpi_device_goodixtls5xx_get_instance_private (self); FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); - GoodixTls5xxPix * raw_frame = calloc (cls->scan_width * cls->scan_height, sizeof (GoodixTls5xxPix)); - goodixtls5xx_decode_frame (raw_frame, len, data); + GoodixTls5xxPix * raw_frame = g_try_new0 (GoodixTls5xxPix, cls->scan_width * cls->scan_height); + if (!raw_frame) + { + fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + goodixtls5xx_decode_frame (raw_frame, cls->scan_width * cls->scan_height, len, data); if (priv->calibration_img) linear_subtract_inplace (raw_frame, priv->calibration_img, cls->scan_width * cls->scan_height); @@ -370,12 +380,18 @@ scan_on_read_img (FpDevice *dev, guint8 *data, guint16 len, } else if (cls->process_frame) { - guint8 * squashed = calloc (cls->scan_height * cls->scan_width, 1); + guint8 * squashed = g_try_malloc0 (cls->scan_height * cls->scan_width); + if (!squashed) + { + g_free (raw_frame); + fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } goodixtls5xx_squash_frame_linear (raw_frame, squashed, cls->scan_height * cls->scan_width); img = cls->process_frame (squashed); - free (squashed); + g_free (squashed); } - free (raw_frame); + g_free (raw_frame); fpi_image_device_image_captured (img_dev, img); @@ -494,11 +510,15 @@ goodixtls5xx_scan_start (FpiDeviceGoodixTls5xx * dev) } void -goodixtls5xx_decode_frame (GoodixTls5xxPix * frame, guint32 frame_size, const guint8 *raw_frame) +goodixtls5xx_decode_frame (GoodixTls5xxPix * frame, guint32 max_pixels, guint32 frame_size, const guint8 *raw_frame) { GoodixTls5xxPix *pix = frame; guint32 start = 0; guint32 end = (frame_size >= 4) ? (frame_size - 4) : frame_size; + guint32 pixel_idx = 0; + + if (!frame || !raw_frame || max_pixels == 0) + return; if (frame_size >= 13 && (frame_size - 13) % 6 == 0) { @@ -506,13 +526,13 @@ goodixtls5xx_decode_frame (GoodixTls5xxPix * frame, guint32 frame_size, const gu end = frame_size - 5; } - for (guint32 i = start; i + 6 <= end; i += 6) + for (guint32 i = start; i + 6 <= end && pixel_idx + 4 <= max_pixels; i += 6) { const guint8 *chunk = raw_frame + i; - *pix++ = ((chunk[0] & 0xf) << 8) + chunk[1]; - *pix++ = (chunk[3] << 4) + (chunk[0] >> 4); - *pix++ = ((chunk[5] & 0xf) << 8) + chunk[2]; - *pix++ = (chunk[4] << 4) + (chunk[5] >> 4); + pix[pixel_idx++] = ((chunk[0] & 0xf) << 8) + chunk[1]; + pix[pixel_idx++] = (chunk[3] << 4) + (chunk[0] >> 4); + pix[pixel_idx++] = ((chunk[5] & 0xf) << 8) + chunk[2]; + pix[pixel_idx++] = (chunk[4] << 4) + (chunk[5] >> 4); } } diff --git a/libfprint/drivers/goodixtls/goodix5xx.h b/libfprint/drivers/goodixtls/goodix5xx.h index 2f3cc59af..c99dda803 100644 --- a/libfprint/drivers/goodixtls/goodix5xx.h +++ b/libfprint/drivers/goodixtls/goodix5xx.h @@ -211,10 +211,12 @@ void goodixtls5xx_scan_start (FpiDeviceGoodixTls5xx * dev); * @note Doesn't decrypt it * * @param frame + * @param max_pixels * @param frame_size * @param raw_frame */ void goodixtls5xx_decode_frame (GoodixTls5xxPix * frame, + guint32 max_pixels, guint32 frame_size, const guint8 *raw_frame); diff --git a/libfprint/drivers/goodixtls/goodixtls.c b/libfprint/drivers/goodixtls/goodixtls.c index 24c3ad4b4..1f59cc048 100644 --- a/libfprint/drivers/goodixtls/goodixtls.c +++ b/libfprint/drivers/goodixtls/goodixtls.c @@ -52,7 +52,7 @@ err_from_ssl (void) "SSL error (0x%lx): %s", code, msg ? msg : "unknown SSL error"); } -#define GOODIX_TLS_CIPHERS "PSK-AES128-CBC-SHA256:ALL:@SECLEVEL=1" +#define GOODIX_TLS_CIPHERS "PSK-AES128-CBC-SHA256:@SECLEVEL=1" static unsigned int tls_server_psk_server_callback (SSL *ssl, @@ -92,18 +92,8 @@ tls_server_psk_server_callback (SSL *ssl, server, server ? server->user_data : NULL); } - const int len = 32; - - fp_warn ("5e0a PSK callback: fallback to zero PSK (len %d, max %d)", len, max_psk_len); - if (len > max_psk_len) - { - fp_err ("max psk length (%d) too short (needs %d)", max_psk_len, len); - return 0; - } - - memset (psk, 0, len); - - return len; + fp_err ("5e0a PSK callback: no valid device PSK available"); + return 0; } static SSL_CTX * @@ -126,7 +116,8 @@ tls_server_config_ctx (SSL_CTX *ctx) { (void) SSL_CTX_set_ecdh_auto (ctx, 1); SSL_CTX_set_dh_auto (ctx, 1); - SSL_CTX_set_cipher_list (ctx, GOODIX_TLS_CIPHERS); + if (SSL_CTX_set_cipher_list (ctx, GOODIX_TLS_CIPHERS) != 1) + g_warning ("5e0a TLS: failed to set CTX cipher list '%s'", GOODIX_TLS_CIPHERS); SSL_CTX_set_min_proto_version (ctx, TLS1_2_VERSION); SSL_CTX_set_max_proto_version (ctx, TLS1_2_VERSION); SSL_CTX_set_psk_server_callback (ctx, tls_server_psk_server_callback); @@ -192,7 +183,8 @@ tls_config_ssl (SSL *ssl) SSL_set_min_proto_version (ssl, TLS1_2_VERSION); SSL_set_max_proto_version (ssl, TLS1_2_VERSION); SSL_set_psk_server_callback (ssl, tls_server_psk_server_callback); - SSL_set_cipher_list (ssl, GOODIX_TLS_CIPHERS); + if (SSL_set_cipher_list (ssl, GOODIX_TLS_CIPHERS) != 1) + g_warning ("5e0a TLS: failed to set SSL cipher list '%s'", GOODIX_TLS_CIPHERS); } static void * @@ -329,6 +321,26 @@ goodix_tls_server_init (GoodixTlsServer *self, GError **error) self->client_fd = socks[1]; self->ssl_layer = SSL_new (self->ssl_ctx); + if (!self->ssl_layer) + { + g_propagate_error (error, err_from_ssl ()); + if (self->sock_fd >= 0) + { + close (self->sock_fd); + self->sock_fd = -1; + } + if (self->client_fd >= 0) + { + close (self->client_fd); + self->client_fd = -1; + } + if (self->ssl_ctx) + { + SSL_CTX_free (self->ssl_ctx); + self->ssl_ctx = NULL; + } + return FALSE; + } SSL_set_app_data (self->ssl_layer, self); tls_config_ssl (self->ssl_layer); SSL_set_fd (self->ssl_layer, self->sock_fd); From eb4853e94bd91dd0c02d1cb3e80a5d7d5ed21501 Mon Sep 17 00:00:00 2001 From: Nix User Date: Wed, 9 Sep 2026 23:42:03 +0530 Subject: [PATCH 11/17] fix tls failures --- libfprint/drivers/goodixtls/goodix.c | 148 ++++++++++++++++------- libfprint/drivers/goodixtls/goodix.h | 19 +++ libfprint/drivers/goodixtls/goodix5e0a.c | 118 ++++++++++++++++-- libfprint/drivers/goodixtls/goodix5xx.c | 13 ++ 4 files changed, 244 insertions(+), 54 deletions(-) diff --git a/libfprint/drivers/goodixtls/goodix.c b/libfprint/drivers/goodixtls/goodix.c index 58016e9fe..701331ff8 100644 --- a/libfprint/drivers/goodixtls/goodix.c +++ b/libfprint/drivers/goodixtls/goodix.c @@ -138,7 +138,7 @@ goodix_receive_none_tolerant (FpDevice *dev, guint8 *data, guint16 length, GoodixNoneCallback callback = (GoodixNoneCallback) cb_info->callback; if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) - g_clear_error (&error); /* Flush error ignored: buffer already empty */ + g_clear_error (&error); /* Buffer already empty. */ callback (dev, cb_info->user_data, error); } @@ -607,14 +607,23 @@ goodix_send_protocol ( if (priv->ack || priv->reply || priv->timeout) { - fp_warn ("A command is already running: 0x%02x", priv->cmd); + /* A command is already running. */ + guint8 busy_cmd = priv->cmd; + fp_warn ("A command is already running: 0x%02x", busy_cmd); if (free_func) free_func ((void *) payload); - /* Fail loudly so the waiting SSM aborts instead of hanging. */ GError *collision_error = g_error_new (G_IO_ERROR, G_IO_ERROR_BUSY, - "A command is already running: 0x%02x", priv->cmd); + "A command is already running: 0x%02x", busy_cmd); goodix_receive_done (dev, NULL, 0, collision_error); + /* Fail the incoming waiter as well, otherwise its SSM stalls. */ + if (callback) + { + GError *incoming_error = + g_error_new (G_IO_ERROR, G_IO_ERROR_BUSY, + "A command is already running: 0x%02x", busy_cmd); + callback (dev, NULL, 0, user_data, incoming_error); + } return; } @@ -640,7 +649,6 @@ goodix_send_protocol ( goodix_receive_done (dev, NULL, 0, error); return; } - ; } void goodix_send_nop (FpDevice *dev, GoodixNoneCallback callback, @@ -652,7 +660,7 @@ goodix_send_nop (FpDevice *dev, GoodixNoneCallback callback, /* Flush command: silence from the MCU indicates success. */ if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -686,7 +694,7 @@ goodix_send_mcu_get_image (FpDevice *dev, GoodixImageCallback callback, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -713,7 +721,7 @@ goodix_send_mcu_switch_to_fdt_down (FpDevice *dev, const guint8 *mode, guint16 l if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; cb = goodix_receive_default; @@ -747,7 +755,7 @@ goodix_send_mcu_switch_to_fdt_up (FpDevice *dev, const guint8 *mode, guint16 len if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; cb = goodix_receive_default; @@ -781,7 +789,7 @@ goodix_send_mcu_switch_to_fdt_mode (FpDevice *dev, const guint8 *mode, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -803,7 +811,7 @@ goodix_send_nav_0 (FpDevice *dev, GoodixDefaultCallback callback, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -829,7 +837,7 @@ goodix_send_mcu_switch_to_idle_mode (FpDevice *dev, guint8 sleep_time, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -860,7 +868,7 @@ goodix_send_write_sensor_register (FpDevice *dev, guint16 address, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -891,7 +899,7 @@ goodix_send_read_sensor_register (FpDevice *dev, guint16 address, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -917,7 +925,7 @@ goodix_send_upload_config_mcu (FpDevice *dev, guint8 *config, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -945,7 +953,7 @@ goodix_send_set_powerdown_scan_frequency (FpDevice *dev, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -970,7 +978,7 @@ goodix_send_enable_chip (FpDevice *dev, gboolean enable, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -999,7 +1007,7 @@ goodix_send_reset (FpDevice *dev, gboolean reset_sensor, guint8 sleep_time, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -1025,7 +1033,7 @@ goodix_send_query_firmware_version (FpDevice *dev, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -1050,7 +1058,7 @@ goodix_send_query_mcu_state (FpDevice *dev, GoodixNoneCallback callback, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -1076,7 +1084,7 @@ goodix_send_request_tls_connection (FpDevice *dev, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -1102,7 +1110,7 @@ goodix_send_tls_successfully_established (FpDevice *dev, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -1129,7 +1137,7 @@ goodix_send_set_drv_state (FpDevice *dev, GoodixNoneCallback cb, if (cb) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (cb); cb_info->user_data = ud; @@ -1154,7 +1162,7 @@ goodix_send_mcu_get_pov_image (FpDevice *dev, GoodixDefaultCallback cb, if (cb) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (cb); cb_info->user_data = ud; @@ -1181,7 +1189,7 @@ goodix_send_set_pov_config (FpDevice *dev, const guint8 *cfg, guint16 len, if (cb) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (cb); cb_info->user_data = ud; @@ -1201,7 +1209,7 @@ goodix_send_read_otp (FpDevice *dev, GoodixDefaultCallback callback, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -1237,7 +1245,7 @@ goodix_send_preset_psk_write (FpDevice *dev, guint32 flags, guint8 *psk, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -1264,7 +1272,47 @@ goodix_send_preset_psk_read (FpDevice *dev, guint32 flags, guint16 length, if (callback) { - cb_info = malloc (sizeof (GoodixCallbackInfo)); + cb_info = g_new0 (GoodixCallbackInfo, 1); + + cb_info->callback = G_CALLBACK (callback); + cb_info->user_data = user_data; + + goodix_send_protocol (dev, GOODIX_CMD_PRESET_PSK_READ, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, + goodix_receive_preset_psk_read, cb_info); + return; + } + + goodix_send_protocol (dev, GOODIX_CMD_PRESET_PSK_READ, (guint8 *) &payload, + sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, NULL, + NULL); +} + +void +goodix_send_preset_psk_read_5e0a (FpDevice *dev, + guint32 flags, + guint32 length, + guint32 offset, + GoodixPresetPskReadCallback callback, + gpointer user_data) +{ + /* 5e0a CMD 0xe4 payload: length + offset + flags + reserved (4B LE each). */ + struct __attribute__((__packed__)) { + guint32 length; + guint32 offset; + guint32 flags; + guint32 reserved; + } payload = { + .length = GUINT32_TO_LE (length), + .offset = GUINT32_TO_LE (offset), + .flags = GUINT32_TO_LE (flags), + .reserved = 0, + }; + GoodixCallbackInfo *cb_info; + + if (callback) + { + cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; @@ -1453,7 +1501,8 @@ goodix_dev_deinit (FpDevice *dev, GError **error) if (!clean_close) goodix_activation_gen_bump (dev); - g_cancellable_cancel (priv->transfer_cancel_tkn); + if (priv->transfer_cancel_tkn) + g_cancellable_cancel (priv->transfer_cancel_tkn); g_clear_object (&priv->transfer_cancel_tkn); if (!clean_close) @@ -1552,6 +1601,15 @@ on_tls_successfully_established (FpDevice *dev, gpointer user_data, FpiDeviceGoodixTls * self = FPI_DEVICE_GOODIXTLS (dev); FpiDeviceGoodixTlsPrivate * priv = fpi_device_goodixtls_get_instance_private (self); + if (!priv->tls_ready_callback) + { + if (error) + g_error_free (error); + return; + } + /* The MCU never ACKs the notification; a timeout here reports success. */ + if (error) + g_error_free (error); ((GoodixNoneCallback) priv->tls_ready_callback->callback)( dev, priv->tls_ready_callback->user_data, NULL); g_clear_pointer (&priv->tls_ready_callback, g_free); @@ -1719,7 +1777,16 @@ on_goodix_request_tls_connection (FpDevice *dev, guint8 *data, if (error) { fp_err ("failed to get tls handshake: %s", error->message); - goodix_send_tls_successfully_established (FP_DEVICE (dev), NULL, NULL); + /* Forward the error instead of faking success on a dead channel. */ + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + if (priv->tls_ready_callback) + { + ((GoodixNoneCallback) priv->tls_ready_callback->callback)( + dev, priv->tls_ready_callback->user_data, error); + g_clear_pointer (&priv->tls_ready_callback, g_free); + } return; } FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (user_data); @@ -1731,19 +1798,6 @@ on_goodix_request_tls_connection (FpDevice *dev, guint8 *data, do_tls_handshake (dev); } -static void -goodix_tls_ready (GoodixTlsServer *server, GError *err, gpointer dev) -{ - if (err) - { - fp_err ("failed to init tls server: %s, code: %d", err->message, - err->code); - return; - } - goodix_send_request_tls_connection (FP_DEVICE (dev), - on_goodix_request_tls_connection, dev); -} - void goodix_tls_init (FpDevice *dev, GoodixNoneCallback callback, gpointer user_data) { @@ -1780,7 +1834,8 @@ goodix_tls_init (FpDevice *dev, GoodixNoneCallback callback, gpointer user_data) return; } - goodix_tls_ready (s, err, self); + goodix_send_request_tls_connection (dev, + on_goodix_request_tls_connection, dev); } gboolean @@ -1795,8 +1850,11 @@ goodix_shutdown_tls (FpDevice *dev, GError **error) gboolean rs = goodix_tls_server_deinit (priv->tls_hop, error); g_free (priv->tls_hop); priv->tls_hop = NULL; + /* Drop any stranded handshake waiter without invoking it. */ + g_clear_pointer (&priv->tls_ready_callback, g_free); return rs; } + g_clear_pointer (&priv->tls_ready_callback, g_free); return TRUE; } @@ -1874,7 +1932,7 @@ goodix_tls_read_image (FpDevice *dev, GoodixImageCallback callback, gpointer user_data) { g_assert (callback); - GoodixCallbackInfo *cb_info = malloc (sizeof (GoodixCallbackInfo)); + GoodixCallbackInfo *cb_info = g_new0 (GoodixCallbackInfo, 1); cb_info->callback = G_CALLBACK (callback); cb_info->user_data = user_data; diff --git a/libfprint/drivers/goodixtls/goodix.h b/libfprint/drivers/goodixtls/goodix.h index ad8a6cab4..6512a97be 100644 --- a/libfprint/drivers/goodixtls/goodix.h +++ b/libfprint/drivers/goodixtls/goodix.h @@ -492,6 +492,25 @@ void goodix_send_preset_psk_read (FpDevice *dev, guint16 length, GoodixPresetPskReadCallback callback, gpointer user_data); +/** + * @brief Read PSK from the device using the 5e0a / Geneva wire framing. + * + * The 5e0a CMD 0xe4 payload reverses the order compared to CMD 0x06: + * length (4B LE) + offset (4B LE) + flags (4B LE) + reserved (4B LE). + * + * @param dev + * @param flags + * @param length + * @param offset byte offset into the PSK slot + * @param callback + * @param user_data + */ +void goodix_send_preset_psk_read_5e0a (FpDevice *dev, + guint32 flags, + guint32 length, + guint32 offset, + GoodixPresetPskReadCallback callback, + gpointer user_data); /** * @brief Request the OTP (One Time Password) from the device * diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c index e6208c6c7..830009f51 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.c +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -100,11 +100,33 @@ enum activate_states { ACTIVATE_READ_OTP, ACTIVATE_CHECK_FW_VER, ACTIVATE_UPLOAD_CONFIG, + ACTIVATE_CHECK_PSK, ACTIVATE_NUM_STATES, }; static void activate_complete (FpiSsm *ssm, FpDevice *dev, GError *error); +/* Read the PSK slot before TLS to latch the MCU crypto state. Best effort: + * failure falls through to TLS and surfaces there. */ +static void +on_psk_hash_read (FpDevice *dev, gboolean success, guint32 flags, + guint8 *psk, guint16 length, gpointer user_data, + GError *error) +{ + FpiSsm *ssm = user_data; + + if (error) + { + fp_warn ("PSK hash read failed: %s", error->message); + g_error_free (error); + } + else + { + fp_dbg ("PSK hash read: success=%d, len=%u", success, length); + } + fpi_ssm_next_state (ssm); +} + static void activate_run_state (FpiSsm *ssm, FpDevice *dev) { @@ -153,9 +175,18 @@ activate_run_state (FpiSsm *ssm, FpDevice *dev) fpi_ssm_jump_to_state (ssm, ACTIVATE_NUM_STATES); return; } - goodix_send_upload_config_mcu (dev, (guint8 *) goodix_5e0a_config, - sizeof (goodix_5e0a_config), NULL, - goodixtls5xx_check_config_upload, ssm); + /* Config is uploaded after TLS completes; advance to the PSK read. */ + fpi_ssm_next_state (ssm); + break; + + case ACTIVATE_CHECK_PSK: + if (self->warm_attempted) + { + fpi_ssm_jump_to_state (ssm, ACTIVATE_NUM_STATES); + return; + } + goodix_send_preset_psk_read_5e0a (dev, GOODIX_5E0A_PSK_FLAGS, 32, 0, + on_psk_hash_read, ssm); break; } } @@ -282,6 +313,28 @@ on_parked_health_reply (FpDevice *dev, gpointer user_data, GError *error) goodix_send_enable_chip (dev, TRUE, on_chip_enabled, NULL); } +static void +on_post_tls_config_uploaded (FpDevice *dev, gboolean success, + gpointer user_data, GError *error) +{ + if (error) + { + fp_err ("failed to upload config after TLS: %s", error->message); + fpi_image_device_activate_complete (FP_IMAGE_DEVICE (dev), error); + return; + } + if (!success) + { + fp_err ("MCU rejected config upload after TLS"); + fpi_image_device_activate_complete ( + FP_IMAGE_DEVICE (dev), + g_error_new (FP_DEVICE_ERROR, FP_DEVICE_ERROR_PROTO, + "failed to upload mcu config after TLS")); + return; + } + goodix_send_enable_chip (dev, TRUE, on_chip_enabled, NULL); +} + static void on_tls_activation_complete (FpDevice *dev, gpointer user_data, GError *error) { @@ -320,8 +373,19 @@ on_tls_activation_complete (FpDevice *dev, gpointer user_data, GError *error) return; } - fp_dbg ("TLS connection ready! Enabling chip..."); - goodix_send_enable_chip (dev, TRUE, on_chip_enabled, NULL); + fp_dbg ("TLS connection ready!"); + + /* Upload config after TLS on the cold path; warm reuse keeps its config. */ + if (self->warm_attempted) + { + goodix_send_enable_chip (dev, TRUE, on_chip_enabled, NULL); + } + else + { + goodix_send_upload_config_mcu (dev, (guint8 *) goodix_5e0a_config, + sizeof (goodix_5e0a_config), NULL, + on_post_tls_config_uploaded, NULL); + } } static void @@ -667,7 +731,7 @@ goodix5e0a_claim_best_frame (FpiDeviceGoodixTls5e0a *self) if (self->best_img == NULL) return NULL; best = self->best_img; - g_message ("5e0a best frame %u/%u: minutiae=%u score-proxy=%u (submitting)", + fp_dbg ("5e0a best frame %u/%u: minutiae=%u score-proxy=%u (submitting)", self->best_frame_no, (guint) GOODIX_5E0A_FRAMES_PER_TOUCH, self->best_minutiae, self->best_minutiae); self->best_img = NULL; @@ -713,7 +777,7 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, if (action != FPI_DEVICE_ACTION_ENROLL && self->best_img != NULL && (data == NULL || len < GOODIX_5E0A_FRAME_WIRE_BYTES)) { - g_message ("5e0a frame %u/%u: short declen=%u, submitting best-so-far %u/%u", + fp_dbg ("5e0a frame %u/%u: short declen=%u, submitting best-so-far %u/%u", self->frame_count + 1, (guint) GOODIX_5E0A_FRAMES_PER_TOUCH, len, self->best_frame_no, (guint) GOODIX_5E0A_FRAMES_PER_TOUCH); @@ -843,7 +907,7 @@ goodix5e0a_keep_best_frame (FpDevice *dev, gpointer ssm, FpImage *img, guint minutiae = img ? goodix5e0a_count_minutiae (img) : 0; self->frame_count++; - g_message ("5e0a frame %u/%u: declen=%u active=%u range=%u minutiae=%u score-proxy=%u", + fp_dbg ("5e0a frame %u/%u: declen=%u active=%u range=%u minutiae=%u score-proxy=%u", self->frame_count, (guint) GOODIX_5E0A_FRAMES_PER_TOUCH, declen, active, range, minutiae, minutiae); @@ -877,9 +941,39 @@ goodix5e0a_on_fdt_up_reply (FpDevice *dev, guint8 *data, guint16 len, { FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + if (self->scan_ssm != ssm) + { + if (err) + g_error_free (err); + return; + } + if (err) { + if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + fpi_ssm_mark_failed (ssm, err); + return; + } fp_dbg ("5e0a D34 reply (tolerant): %s", err->message); + if (self->retry_guard) + { + /* A timeout here means the finger is still down, not released. + * Re-issue while the guard is held; stop after 30s. */ + if (g_get_monotonic_time () - self->retry_guard_mono > 30 * G_USEC_PER_SEC) + { + self->retry_guard = FALSE; + fp_dbg ("5e0a retry guard: orphaned hold past 30s, failing claim"); + fpi_ssm_mark_failed (ssm, err); + return; + } + g_error_free (err); + fp_dbg ("5e0a retry guard: finger still present, re-issuing FDT UP"); + send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_UP, + goodix_5e0a_up_u01, sizeof (goodix_5e0a_up_u01), + 2000, goodix5e0a_on_fdt_up_reply, ssm); + return; + } g_error_free (err); } else @@ -1030,6 +1124,7 @@ goodix5e0a_deactivate (FpImageDevice *img_dev) { FpDevice *dev = FP_DEVICE (img_dev); FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + gboolean scan_was_active; goodix5e0a_reset_touch_frames (self); goodix_activation_gen_bump (dev); @@ -1045,13 +1140,18 @@ goodix5e0a_deactivate (FpImageDevice *img_dev) } goodix_reset_state (dev); + scan_was_active = (self->scan_ssm != NULL); if (self->scan_ssm != NULL) { fpi_ssm_free (self->scan_ssm); self->scan_ssm = NULL; } - if (goodix_tls_is_alive (dev) && self->warm_ok) + /* Only park when deactivation arrived idle; a scan torn down mid-flight + * can leave dangling replies that poison the next reuse. */ + if (scan_was_active && goodix_tls_is_alive (dev) && self->warm_ok) + fp_dbg ("5e0a park invalidated: scan active at deactivate"); + if (goodix_tls_is_alive (dev) && self->warm_ok && !scan_was_active) { goodix_stop_read_loop (dev); self->tls_parked = TRUE; diff --git a/libfprint/drivers/goodixtls/goodix5xx.c b/libfprint/drivers/goodixtls/goodix5xx.c index ff7a8103f..b2680d1cb 100644 --- a/libfprint/drivers/goodixtls/goodix5xx.c +++ b/libfprint/drivers/goodixtls/goodix5xx.c @@ -70,6 +70,14 @@ send_switch_mode (FpDevice * dev, gpointer ssm, void (*mode_switch)(FpDevice *, gpointer)) { FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); + /* No subclass provides get_mcu_cfg without also overriding change_state + * (5e0a does), but dereferencing it unguarded would NULL-crash any future + * base-scan user — same guard shape as the sibling FDT branches. */ + if (!cls->get_mcu_cfg) + { + fpi_ssm_next_state (ssm); + return; + } GoodixTls5xxMcuConfig cfg = cls->get_mcu_cfg (); mode_switch (dev, cfg.data, cfg.data_len, cfg.free_fn, goodixtls5xx_check_none_cmd, ssm); @@ -418,6 +426,11 @@ scan_run_state (FpiSsm * ssm, FpDevice * dev) case SCAN_STAGE_SWITCH_TO_FDT_MODE: { + if (!cls->get_mcu_cfg) + { + fpi_ssm_next_state (ssm); + break; + } GoodixTls5xxMcuConfig cfg = cls->get_mcu_cfg (); goodix_send_mcu_switch_to_fdt_mode (dev, cfg.data, cfg.data_len, cfg.free_fn, goodixtls5xx_check_none, ssm); } From 269a313a38f3f03624510b5ff17c8d45a6eae1c6 Mon Sep 17 00:00:00 2001 From: Nix User Date: Thu, 10 Sep 2026 00:21:43 +0530 Subject: [PATCH 12/17] goodixtls: harden helpers, drop dead code, address review findings Consolidate callback allocation behind make_cb_info(), mark file-internal helpers static, and remove unused command wrappers exposed by the static hardening (reset, psk_write, pov, drv_state, idle_mode, sensor-register and powerdown helpers, plus the unwired FDT-DOWN retry table). Make MCU_GET_IMAGE capture payload polymorphic via base-class capture_payload fields instead of a driver-string check, move wire tables and PSK from goodix5e0a.h to file-static, migrate g_memdup to g_memdup2, use g_malloc consistently, hoist declarations, type SSM helpers as FpiSsm, and narrow USB re-enumeration detection to bus+port+vid+pid. Also propagate TLS proxy write errors and guard the TLS server read. --- libfprint/drivers/goodixtls/goodix.c | 608 ++++++--------------- libfprint/drivers/goodixtls/goodix.h | 224 +------- libfprint/drivers/goodixtls/goodix5e0a.c | 238 +++++--- libfprint/drivers/goodixtls/goodix5e0a.h | 78 +-- libfprint/drivers/goodixtls/goodix5xx.c | 90 +-- libfprint/drivers/goodixtls/goodix_proto.c | 10 +- libfprint/drivers/goodixtls/goodix_proto.h | 5 +- libfprint/drivers/goodixtls/goodixtls.c | 14 +- 8 files changed, 424 insertions(+), 843 deletions(-) diff --git a/libfprint/drivers/goodixtls/goodix.c b/libfprint/drivers/goodixtls/goodix.c index 701331ff8..0786190cc 100644 --- a/libfprint/drivers/goodixtls/goodix.c +++ b/libfprint/drivers/goodixtls/goodix.c @@ -82,6 +82,20 @@ typedef struct G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (FpiDeviceGoodixTls, fpi_device_goodixtls, FP_TYPE_IMAGE_DEVICE); +static GoodixCallbackInfo * +make_cb_info (GCallback callback, gpointer user_data) +{ + GoodixCallbackInfo *cb_info = g_new0 (GoodixCallbackInfo, 1); + + cb_info->callback = callback; + cb_info->user_data = user_data; + + return cb_info; +} + +/* Defined below; needed early by goodix_receive_data_cb. */ +static void goodix_receive_data (FpDevice *dev); + gchar * data_to_str (guint8 *data, guint32 length) { @@ -93,7 +107,7 @@ data_to_str (guint8 *data, guint32 length) return string; } -void +static void goodix_receive_done (FpDevice *dev, guint8 *data, guint16 length, GError *error) { @@ -130,7 +144,7 @@ goodix_receive_none (FpDevice *dev, guint8 *data, guint16 length, callback (dev, cb_info->user_data, error); } -void +static void goodix_receive_none_tolerant (FpDevice *dev, guint8 *data, guint16 length, gpointer user_data, GError *error) { @@ -153,7 +167,7 @@ goodix_receive_default (FpDevice *dev, guint8 *data, guint16 length, callback (dev, data, length, cb_info->user_data, error); } -void +static void goodix_receive_success (FpDevice *dev, guint8 *data, guint16 length, gpointer user_data, GError *error) { @@ -177,33 +191,7 @@ goodix_receive_success (FpDevice *dev, guint8 *data, guint16 length, callback (dev, data[0] == 0x00 ? FALSE : TRUE, cb_info->user_data, NULL); } -void -goodix_receive_reset (FpDevice *dev, guint8 *data, guint16 length, - gpointer user_data, GError *error) -{ - g_autofree GoodixCallbackInfo *cb_info = user_data; - GoodixResetCallback callback = (GoodixResetCallback) cb_info->callback; - - if (error) - { - callback (dev, FALSE, 0, cb_info->user_data, error); - return; - } - - if (length != sizeof (guint8) + sizeof (guint16)) - { - g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, - "Invalid reset reply length: %d", length); - callback (dev, FALSE, 0, cb_info->user_data, error); - return; - } - - callback (dev, data[0] == 0x00 ? FALSE : TRUE, - GUINT16_FROM_LE (*(guint16 *) (data + sizeof (guint8))), - cb_info->user_data, NULL); -} - -void +static void goodix_receive_preset_psk_read (FpDevice *dev, guint8 *data, guint16 length, gpointer user_data, GError *error) { @@ -257,32 +245,7 @@ goodix_receive_preset_psk_read (FpDevice *dev, guint8 *data, guint16 length, cb_info->user_data, NULL); } -void -goodix_receive_preset_psk_write (FpDevice *dev, guint8 *data, - guint16 length, gpointer user_data, - GError *error) -{ - g_autofree GoodixCallbackInfo *cb_info = user_data; - GoodixSuccessCallback callback = (GoodixSuccessCallback) cb_info->callback; - - if (error) - { - callback (dev, FALSE, cb_info->user_data, error); - return; - } - - if (length < sizeof (guint8)) - { - g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, - "Invalid preset PSK write reply length: %d", length); - callback (dev, FALSE, cb_info->user_data, error); - return; - } - - callback (dev, data[0] == 0x00 ? TRUE : FALSE, cb_info->user_data, NULL); -} - -void +static void goodix_receive_firmware_version (FpDevice *dev, guint8 *data, guint16 length, gpointer user_data, GError *error) @@ -306,7 +269,7 @@ goodix_receive_firmware_version (FpDevice *dev, guint8 *data, callback (dev, payload, cb_info->user_data, NULL); } -void +static void goodix_receive_ack (FpDevice *dev, guint8 *data, guint16 length, gpointer user_data, GError *error) { @@ -355,7 +318,7 @@ goodix_receive_ack (FpDevice *dev, guint8 *data, guint16 length, priv->ack = FALSE; } -void +static void goodix_receive_protocol (FpDevice *dev, guint8 *data, guint32 length) { FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); @@ -398,7 +361,7 @@ goodix_receive_protocol (FpDevice *dev, guint8 *data, guint32 length) goodix_receive_done (dev, payload, payload_len, NULL); } -void +static void goodix_receive_pack (FpDevice *dev, guint8 *data, guint32 length) { FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); @@ -448,7 +411,7 @@ goodix_receive_pack (FpDevice *dev, guint8 *data, guint32 length) priv->length = 0; } -void +static void goodix_receive_data_cb (FpiUsbTransfer *transfer, FpDevice *dev, gpointer user_data, GError *error) { @@ -478,7 +441,7 @@ goodix_receive_data_cb (FpiUsbTransfer *transfer, FpDevice *dev, goodix_receive_data (dev); } -void +static void goodix_receive_timeout_cb (FpDevice *dev, gpointer user_data) { FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); @@ -527,7 +490,7 @@ goodix_stop_read_loop (FpDevice *dev) priv->length = 0; } -void +static void goodix_receive_data (FpDevice *dev) { FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); @@ -545,7 +508,7 @@ goodix_receive_data (FpDevice *dev) goodix_receive_data_cb, NULL); } -gboolean +static gboolean goodix_send_data (FpDevice *dev, guint8 *data, guint32 length, GDestroyNotify free_func, GError **error) { @@ -577,7 +540,7 @@ goodix_send_data (FpDevice *dev, guint8 *data, guint32 length, return TRUE; } -gboolean +static gboolean goodix_send_pack (FpDevice *dev, guint8 flags, guint8 *payload, guint16 length, GDestroyNotify free_func, GError **error) @@ -609,10 +572,11 @@ goodix_send_protocol ( { /* A command is already running. */ guint8 busy_cmd = priv->cmd; + GError *collision_error; fp_warn ("A command is already running: 0x%02x", busy_cmd); if (free_func) free_func ((void *) payload); - GError *collision_error = + collision_error = g_error_new (G_IO_ERROR, G_IO_ERROR_BUSY, "A command is already running: 0x%02x", busy_cmd); goodix_receive_done (dev, NULL, 0, collision_error); @@ -660,10 +624,7 @@ goodix_send_nop (FpDevice *dev, GoodixNoneCallback callback, /* Flush command: silence from the MCU indicates success. */ if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); goodix_send_protocol (dev, GOODIX_CMD_NOP, (guint8 *) &payload, sizeof (payload), NULL, FALSE, GOODIX_NOP_TIMEOUT, FALSE, @@ -675,29 +636,25 @@ goodix_send_nop (FpDevice *dev, GoodixNoneCallback callback, NULL, FALSE, GOODIX_NOP_TIMEOUT, FALSE, NULL, NULL); } -guint8 goodix5e0a_capture_payload[10] = {0x05, 0x00, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00}; - -void +static void goodix_send_mcu_get_image (FpDevice *dev, GoodixImageCallback callback, gpointer user_data) { + FpiDeviceGoodixTlsClass *class = FPI_DEVICE_GOODIXTLS_GET_CLASS (dev); GoodixCallbackInfo *cb_info; GoodixDefault payload_default = {.unused_flags = 0x01}; - guint8 *payload = (guint8 *) &payload_default; + const guint8 *payload = (const guint8 *) &payload_default; guint16 len = sizeof (payload_default); - if (g_strcmp0 (fp_device_get_driver (dev), "goodixtls5e0a") == 0) + if (class->capture_payload != NULL && class->capture_payload_len > 0) { - payload = goodix5e0a_capture_payload; - len = sizeof (goodix5e0a_capture_payload); + payload = class->capture_payload; + len = class->capture_payload_len; } if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); goodix_send_protocol (dev, GOODIX_CMD_MCU_GET_IMAGE, payload, len, NULL, TRUE, GOODIX_TIMEOUT, TRUE, @@ -721,21 +678,19 @@ goodix_send_mcu_switch_to_fdt_down (FpDevice *dev, const guint8 *mode, guint16 l if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); cb = goodix_receive_default; } if (mode && length > 0 && mode[0] == 0x01) { - guint8 * payload = malloc (sizeof (guint8) * (length + 1)); + guint8 *payload = g_malloc (sizeof (guint8) * (length + 1)); memcpy (payload + 1, mode, length); payload[0] = 0xc; if (free_func) free_func ((void *) mode); goodix_send_protocol (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, payload, length + 1, - free, TRUE, 0, TRUE, cb, cb_info); + g_free, TRUE, 0, TRUE, cb, cb_info); } else { @@ -755,21 +710,19 @@ goodix_send_mcu_switch_to_fdt_up (FpDevice *dev, const guint8 *mode, guint16 len if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); cb = goodix_receive_default; } if (mode && length > 0 && mode[0] == 0x01) { - guint8 * payload = malloc (sizeof (guint8) * (length + 1)); + guint8 *payload = g_malloc (sizeof (guint8) * (length + 1)); memcpy (payload + 1, mode, length); payload[0] = 0xe; if (free_func) free_func ((void *) mode); goodix_send_protocol (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_UP, payload, length + 1, - free, TRUE, 0, TRUE, cb, cb_info); + g_free, TRUE, 0, TRUE, cb, cb_info); } else { @@ -789,10 +742,7 @@ goodix_send_mcu_switch_to_fdt_mode (FpDevice *dev, const guint8 *mode, if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); cb = goodix_receive_none; } @@ -811,10 +761,7 @@ goodix_send_nav_0 (FpDevice *dev, GoodixDefaultCallback callback, if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); goodix_send_protocol (dev, GOODIX_CMD_NAV_0, (guint8 *) &payload, sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, @@ -827,63 +774,6 @@ goodix_send_nav_0 (FpDevice *dev, GoodixDefaultCallback callback, NULL); } -void -goodix_send_mcu_switch_to_idle_mode (FpDevice *dev, guint8 sleep_time, - GoodixNoneCallback callback, - gpointer user_data) -{ - GoodixMcuSwitchToIdleMode payload = {.sleep_time = sleep_time}; - GoodixCallbackInfo *cb_info; - - if (callback) - { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; - - goodix_send_protocol (dev, GOODIX_CMD_MCU_SWITCH_TO_IDLE_MODE, - (guint8 *) &payload, sizeof (payload), NULL, TRUE, - GOODIX_TIMEOUT, FALSE, goodix_receive_none, cb_info); - return; - } - - goodix_send_protocol (dev, GOODIX_CMD_MCU_SWITCH_TO_IDLE_MODE, - (guint8 *) &payload, sizeof (payload), NULL, TRUE, - GOODIX_TIMEOUT, FALSE, NULL, NULL); -} - -void -goodix_send_write_sensor_register (FpDevice *dev, guint16 address, - guint16 value, - GoodixNoneCallback callback, - gpointer user_data) -{ - /* Only support one address and one value. */ - - GoodixWriteSensorRegister payload = {.multiples = FALSE, - .address = GUINT16_TO_LE (address), - .value = GUINT16_TO_LE (value)}; - GoodixCallbackInfo *cb_info; - - if (callback) - { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; - - goodix_send_protocol (dev, GOODIX_CMD_WRITE_SENSOR_REGISTER, - (guint8 *) &payload, sizeof (payload), NULL, TRUE, - GOODIX_TIMEOUT, FALSE, goodix_receive_none, cb_info); - return; - } - - goodix_send_protocol (dev, GOODIX_CMD_WRITE_SENSOR_REGISTER, - (guint8 *) &payload, sizeof (payload), NULL, TRUE, - GOODIX_TIMEOUT, FALSE, NULL, NULL); -} - void goodix_send_read_sensor_register (FpDevice *dev, guint16 address, guint8 length, @@ -899,10 +789,7 @@ goodix_send_read_sensor_register (FpDevice *dev, guint16 address, if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); goodix_send_protocol (dev, GOODIX_CMD_READ_SENSOR_REGISTER, (guint8 *) &payload, sizeof (payload), NULL, TRUE, @@ -925,10 +812,7 @@ goodix_send_upload_config_mcu (FpDevice *dev, guint8 *config, if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); goodix_send_protocol (dev, GOODIX_CMD_UPLOAD_CONFIG_MCU, config, length, free_func, TRUE, GOODIX_TIMEOUT, TRUE, @@ -940,35 +824,6 @@ goodix_send_upload_config_mcu (FpDevice *dev, guint8 *config, free_func, TRUE, GOODIX_TIMEOUT, TRUE, NULL, NULL); } -void -goodix_send_set_powerdown_scan_frequency (FpDevice *dev, - guint16 powerdown_scan_frequency, - GoodixSuccessCallback callback, - gpointer user_data) -{ - GoodixSetPowerdownScanFrequency payload = { - .powerdown_scan_frequency = GUINT16_TO_LE (powerdown_scan_frequency) - }; - GoodixCallbackInfo *cb_info; - - if (callback) - { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; - - goodix_send_protocol (dev, GOODIX_CMD_SET_POWERDOWN_SCAN_FREQUENCY, - (guint8 *) &payload, sizeof (payload), NULL, TRUE, - GOODIX_TIMEOUT, TRUE, goodix_receive_success, cb_info); - return; - } - - goodix_send_protocol (dev, GOODIX_CMD_SET_POWERDOWN_SCAN_FREQUENCY, - (guint8 *) &payload, sizeof (payload), NULL, TRUE, - GOODIX_TIMEOUT, TRUE, NULL, NULL); -} - void goodix_send_enable_chip (FpDevice *dev, gboolean enable, GoodixNoneCallback callback, gpointer user_data) @@ -978,10 +833,7 @@ goodix_send_enable_chip (FpDevice *dev, gboolean enable, if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); goodix_send_protocol (dev, GOODIX_CMD_ENABLE_CHIP, (guint8 *) &payload, sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, FALSE, @@ -994,35 +846,6 @@ goodix_send_enable_chip (FpDevice *dev, gboolean enable, NULL); } -void -goodix_send_reset (FpDevice *dev, gboolean reset_sensor, guint8 sleep_time, - GoodixResetCallback callback, gpointer user_data) -{ - /* Only support reset sensor. */ - - GoodixReset payload = {.soft_reset_mcu = FALSE, - .reset_sensor = reset_sensor ? TRUE : FALSE, - .sleep_time = sleep_time}; - GoodixCallbackInfo *cb_info; - - if (callback) - { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; - - goodix_send_protocol (dev, GOODIX_CMD_RESET, (guint8 *) &payload, - sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, - goodix_receive_reset, cb_info); - return; - } - - goodix_send_protocol (dev, GOODIX_CMD_RESET, (guint8 *) &payload, - sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, NULL, - NULL); -} - void goodix_send_query_firmware_version (FpDevice *dev, GoodixFirmwareVersionCallback callback, @@ -1033,10 +856,7 @@ goodix_send_query_firmware_version (FpDevice *dev, if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); goodix_send_protocol (dev, GOODIX_CMD_FIRMWARE_VERSION, (guint8 *) &payload, sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, @@ -1058,10 +878,7 @@ goodix_send_query_mcu_state (FpDevice *dev, GoodixNoneCallback callback, if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); goodix_send_protocol (dev, GOODIX_CMD_QUERY_MCU_STATE, (guint8 *) &payload, sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, FALSE, @@ -1074,7 +891,7 @@ goodix_send_query_mcu_state (FpDevice *dev, GoodixNoneCallback callback, NULL); } -void +static void goodix_send_request_tls_connection (FpDevice *dev, GoodixDefaultCallback callback, gpointer user_data) @@ -1084,10 +901,7 @@ goodix_send_request_tls_connection (FpDevice *dev, if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); goodix_send_protocol (dev, GOODIX_CMD_REQUEST_TLS_CONNECTION, (guint8 *) &payload, sizeof (payload), NULL, TRUE, 0, @@ -1100,7 +914,7 @@ goodix_send_request_tls_connection (FpDevice *dev, GOODIX_TIMEOUT, TRUE, NULL, NULL); } -void +static void goodix_send_tls_successfully_established (FpDevice *dev, GoodixNoneCallback callback, gpointer user_data) @@ -1110,10 +924,7 @@ goodix_send_tls_successfully_established (FpDevice *dev, if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); /* Timeout needs to be at least 10ms. */ goodix_send_protocol (dev, GOODIX_CMD_TLS_SUCCESSFULLY_ESTABLISHED, @@ -1127,79 +938,6 @@ goodix_send_tls_successfully_established (FpDevice *dev, 2000, TRUE, NULL, NULL); } -void -goodix_send_set_drv_state (FpDevice *dev, GoodixNoneCallback cb, - gpointer ud) -{ - /* Send 2-byte payload [0x01, 0x00]. */ - GoodixDefault payload = {.unused_flags = 0x01}; - GoodixCallbackInfo *cb_info; - - if (cb) - { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (cb); - cb_info->user_data = ud; - - goodix_send_protocol (dev, GOODIX_CMD_SET_DRV_STATE, (guint8 *) &payload, - sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, FALSE, - goodix_receive_none, cb_info); - return; - } - - goodix_send_protocol (dev, GOODIX_CMD_SET_DRV_STATE, (guint8 *) &payload, - sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, FALSE, - NULL, NULL); -} - -void -goodix_send_mcu_get_pov_image (FpDevice *dev, GoodixDefaultCallback cb, - gpointer ud) -{ - GoodixNone payload = {}; - GoodixCallbackInfo *cb_info; - - if (cb) - { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (cb); - cb_info->user_data = ud; - - goodix_send_protocol (dev, GOODIX_CMD_MCU_GET_POV_IMAGE, - (guint8 *) &payload, sizeof (payload), NULL, TRUE, - GOODIX_TIMEOUT, TRUE, goodix_receive_default, - cb_info); - return; - } - - goodix_send_protocol (dev, GOODIX_CMD_MCU_GET_POV_IMAGE, (guint8 *) &payload, - sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, - NULL, NULL); -} - -void -goodix_send_set_pov_config (FpDevice *dev, const guint8 *cfg, guint16 len, - GDestroyNotify ff, GoodixNoneCallback cb, - gpointer ud) -{ - GoodixCallbackInfo *cb_info = NULL; - GoodixCmdCallback tramp = NULL; - - if (cb) - { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (cb); - cb_info->user_data = ud; - tramp = goodix_receive_none; - } - - goodix_send_protocol (dev, GOODIX_CMD_SET_POV_CONFIG, cfg, len, ff, TRUE, - GOODIX_TIMEOUT, FALSE, tramp, cb_info); -} - void goodix_send_read_otp (FpDevice *dev, GoodixDefaultCallback callback, gpointer user_data) @@ -1209,10 +947,7 @@ goodix_send_read_otp (FpDevice *dev, GoodixDefaultCallback callback, if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); goodix_send_protocol (dev, GOODIX_CMD_READ_OTP, (guint8 *) &payload, sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, @@ -1225,40 +960,27 @@ goodix_send_read_otp (FpDevice *dev, GoodixDefaultCallback callback, NULL, NULL); } -void -goodix_send_preset_psk_write (FpDevice *dev, guint32 flags, guint8 *psk, - guint16 length, GDestroyNotify free_func, - GoodixSuccessCallback callback, - gpointer user_data) +/* Shared 0xe4 PSK-read sender. The two public wrappers below build + * differently-shaped request bodies (generic flags+length vs 5e0a + * length+offset+flags+reserved) around this single protocol call. */ +static void +goodix_send_preset_psk_read_payload (FpDevice *dev, + const guint8 *payload, + guint16 length, + GoodixPresetPskReadCallback callback, + gpointer user_data) { - /* Only support one flag, one payload, and one length. */ - - guint8 *payload = g_malloc (sizeof (GoodixPresetPsk) + length); - GoodixPresetPsk *preset_psk = (GoodixPresetPsk *) payload; - GoodixCallbackInfo *cb_info; - - preset_psk->flags = GUINT32_TO_LE (flags); - preset_psk->length = GUINT32_TO_LE (length); - memcpy (payload + sizeof (GoodixPresetPsk), psk, length); - if (free_func) - free_func (psk); + GoodixCallbackInfo *cb_info = NULL; + GoodixCmdCallback cb = NULL; if (callback) { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; - - goodix_send_protocol (dev, GOODIX_CMD_PRESET_PSK_WRITE, payload, - sizeof (GoodixPresetPsk) + length, g_free, TRUE, GOODIX_TIMEOUT, - TRUE, goodix_receive_preset_psk_write, cb_info); - return; + cb_info = make_cb_info (G_CALLBACK (callback), user_data); + cb = goodix_receive_preset_psk_read; } - goodix_send_protocol (dev, GOODIX_CMD_PRESET_PSK_WRITE, payload, - sizeof (GoodixPresetPsk) + length, g_free, TRUE, GOODIX_TIMEOUT, - TRUE, NULL, NULL); + goodix_send_protocol (dev, GOODIX_CMD_PRESET_PSK_READ, payload, length, + NULL, TRUE, GOODIX_TIMEOUT, TRUE, cb, cb_info); } void @@ -1268,24 +990,9 @@ goodix_send_preset_psk_read (FpDevice *dev, guint32 flags, guint16 length, { GoodixPresetPsk payload = {.flags = GUINT32_TO_LE (flags), .length = GUINT32_TO_LE (length)}; - GoodixCallbackInfo *cb_info; - - if (callback) - { - cb_info = g_new0 (GoodixCallbackInfo, 1); - - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; - - goodix_send_protocol (dev, GOODIX_CMD_PRESET_PSK_READ, (guint8 *) &payload, - sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, - goodix_receive_preset_psk_read, cb_info); - return; - } - goodix_send_protocol (dev, GOODIX_CMD_PRESET_PSK_READ, (guint8 *) &payload, - sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, NULL, - NULL); + goodix_send_preset_psk_read_payload (dev, (const guint8 *) &payload, + sizeof (payload), callback, user_data); } void @@ -1308,24 +1015,9 @@ goodix_send_preset_psk_read_5e0a (FpDevice *dev, .flags = GUINT32_TO_LE (flags), .reserved = 0, }; - GoodixCallbackInfo *cb_info; - - if (callback) - { - cb_info = g_new0 (GoodixCallbackInfo, 1); - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; - - goodix_send_protocol (dev, GOODIX_CMD_PRESET_PSK_READ, (guint8 *) &payload, - sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, - goodix_receive_preset_psk_read, cb_info); - return; - } - - goodix_send_protocol (dev, GOODIX_CMD_PRESET_PSK_READ, (guint8 *) &payload, - sizeof (payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, NULL, - NULL); + goodix_send_preset_psk_read_payload (dev, (const guint8 *) &payload, + sizeof (payload), callback, user_data); } gboolean @@ -1360,10 +1052,13 @@ goodix_dev_init (FpDevice *dev, GError **error) guint16 vid = g_usb_device_get_vid (usb); guint16 pid = g_usb_device_get_pid (usb); + /* Address is unstable across suspend and re-enumeration; bus + * identifies the host controller and port the topology, so compare + * bus+port+vid+pid. The address snapshot is kept for diagnostics + * only. */ if (priv->usb_identity_valid - && (bus != priv->last_usb_bus || addr != priv->last_usb_addr - || port != priv->last_usb_port || vid != priv->last_usb_vid - || pid != priv->last_usb_pid)) + && (bus != priv->last_usb_bus || port != priv->last_usb_port + || vid != priv->last_usb_vid || pid != priv->last_usb_pid)) { reenumerated = TRUE; priv->clean_close = FALSE; @@ -1532,15 +1227,15 @@ goodix_dev_deinit (FpDevice *dev, GError **error) return released; } -void +static void goodix_read_tls (FpDevice *dev, GoodixTlsCallback callback, gpointer user_data) { - - fp_dbg ("goodix_read_tls()"); FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); FpiDeviceGoodixTlsPrivate *priv = fpi_device_goodixtls_get_instance_private (self); + + fp_dbg ("goodix_read_tls()"); priv->callback = callback; priv->user_data = user_data; priv->reply = TRUE; @@ -1560,18 +1255,19 @@ on_goodix_tls_read_handshake (FpDevice *dev, guint8 *data, GError *error) { FpiSsm *ssm = user_data; + FpiDeviceGoodixTls *self; + FpiDeviceGoodixTlsPrivate *priv; + int sent; if (error) { fpi_ssm_mark_failed (ssm, error); return; } - FpiDeviceGoodixTls *self = - FPI_DEVICE_GOODIXTLS (fpi_ssm_get_data (user_data)); - FpiDeviceGoodixTlsPrivate *priv = - fpi_device_goodixtls_get_instance_private (self); + self = FPI_DEVICE_GOODIXTLS (fpi_ssm_get_data (user_data)); + priv = fpi_device_goodixtls_get_instance_private (self); - int sent = goodix_tls_client_write (priv->tls_hop, data, length); + sent = goodix_tls_client_write (priv->tls_hop, data, length); if (sent < 0) { @@ -1597,10 +1293,11 @@ static void on_tls_successfully_established (FpDevice *dev, gpointer user_data, GError *error) { - fp_dbg ("HANDSHAKE DONE"); FpiDeviceGoodixTls * self = FPI_DEVICE_GOODIXTLS (dev); FpiDeviceGoodixTlsPrivate * priv = fpi_device_goodixtls_get_instance_private (self); + + fp_dbg ("HANDSHAKE DONE"); if (!priv->tls_ready_callback) { if (error) @@ -1656,13 +1353,14 @@ tls_accept_wait_ok (FpDevice *dev) static void tls_handshake_done (FpiSsm *ssm, FpDevice *dev, GError *error) { + FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); + FpiDeviceGoodixTlsPrivate *priv = + fpi_device_goodixtls_get_instance_private (self); + if (error) { fp_err ("failed to do tls handshake: %s (code: %d)", error->message, error->code); - FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); - FpiDeviceGoodixTlsPrivate *priv = - fpi_device_goodixtls_get_instance_private (self); if (priv->tls_ready_callback) { ((GoodixNoneCallback) priv->tls_ready_callback->callback)( @@ -1680,14 +1378,13 @@ tls_handshake_done (FpiSsm *ssm, FpDevice *dev, GError *error) * timeout instead of failing here with a clear TLS error. */ if (!tls_accept_wait_ok (dev)) { - GError *accept_error = fpi_device_error_new_msg ( + GError *accept_error; + + accept_error = fpi_device_error_new_msg ( FP_DEVICE_ERROR_GENERAL, "TLS handshake failed: device did not complete the handshake " "(likely PSK mismatch; see log for accept error)"); fp_err ("%s", accept_error->message); - FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); - FpiDeviceGoodixTlsPrivate *priv = - fpi_device_goodixtls_get_instance_private (self); if (priv->tls_ready_callback) { ((GoodixNoneCallback) priv->tls_ready_callback->callback)( @@ -1713,11 +1410,13 @@ tls_handshake_run (FpiSsm *ssm, FpDevice *dev) fpi_device_goodixtls_get_instance_private (self); int stage = fpi_ssm_get_cur_state (ssm); + guint8 buff[1024]; + int size; + GError *err = NULL; if (stage == TLS_HANDSHAKE_STAGE_HELLO_S) { - guint8 buff[1024]; - int size = goodix_tls_client_read (priv->tls_hop, buff, sizeof (buff)); + size = goodix_tls_client_read (priv->tls_hop, buff, sizeof (buff)); if (size < 0) { fpi_ssm_mark_failed (ssm, g_error_new (g_io_error_quark (), size, @@ -1725,7 +1424,6 @@ tls_handshake_run (FpiSsm *ssm, FpDevice *dev) "hello")); return; } - GError *err = NULL; if (!goodix_send_pack (dev, GOODIX_FLAGS_TLS, buff, size, NULL, &err)) { fpi_ssm_mark_failed (ssm, err); @@ -1742,8 +1440,7 @@ tls_handshake_run (FpiSsm *ssm, FpDevice *dev) else if (stage == TLS_HANDSHAKE_STAGE_CHANGE_CIPHER_S) { fp_dbg ("Reading to proxy back"); - guint8 buff[1024]; - int size = goodix_tls_client_read (priv->tls_hop, buff, sizeof (buff)); + size = goodix_tls_client_read (priv->tls_hop, buff, sizeof (buff)); if (size < 0) { fpi_ssm_mark_failed (ssm, g_error_new (g_io_error_quark (), size, @@ -1752,7 +1449,6 @@ tls_handshake_run (FpiSsm *ssm, FpDevice *dev) return; } - GError *err = NULL; if (!goodix_send_pack (dev, GOODIX_FLAGS_TLS, buff, size, NULL, &err)) { fpi_ssm_mark_failed (ssm, err); @@ -1774,13 +1470,16 @@ on_goodix_request_tls_connection (FpDevice *dev, guint8 *data, guint16 length, gpointer user_data, GError *error) { + FpiDeviceGoodixTls *self; + FpiDeviceGoodixTlsPrivate *priv; + int sent; + if (error) { fp_err ("failed to get tls handshake: %s", error->message); /* Forward the error instead of faking success on a dead channel. */ - FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); - FpiDeviceGoodixTlsPrivate *priv = - fpi_device_goodixtls_get_instance_private (self); + self = FPI_DEVICE_GOODIXTLS (dev); + priv = fpi_device_goodixtls_get_instance_private (self); if (priv->tls_ready_callback) { ((GoodixNoneCallback) priv->tls_ready_callback->callback)( @@ -1789,11 +1488,27 @@ on_goodix_request_tls_connection (FpDevice *dev, guint8 *data, } return; } - FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (user_data); - FpiDeviceGoodixTlsPrivate *priv = - fpi_device_goodixtls_get_instance_private (self); + self = FPI_DEVICE_GOODIXTLS (user_data); + priv = fpi_device_goodixtls_get_instance_private (self); - goodix_tls_client_write (priv->tls_hop, data, length); + sent = goodix_tls_client_write (priv->tls_hop, data, length); + + if (sent < 0) + { + GError *write_error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, + "failed to send data to tls server"); + if (priv->tls_ready_callback) + { + ((GoodixNoneCallback) priv->tls_ready_callback->callback)( + dev, priv->tls_ready_callback->user_data, write_error); + g_clear_pointer (&priv->tls_ready_callback, g_free); + } + else + { + g_error_free (write_error); + } + return; + } do_tls_handshake (dev); } @@ -1801,20 +1516,20 @@ on_goodix_request_tls_connection (FpDevice *dev, guint8 *data, void goodix_tls_init (FpDevice *dev, GoodixNoneCallback callback, gpointer user_data) { - fp_dbg ("Starting up goodix tls server"); FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); FpiDeviceGoodixTlsPrivate *priv = fpi_device_goodixtls_get_instance_private (self); + GoodixTlsServer *s; + GError *err = NULL; + + fp_dbg ("Starting up goodix tls server"); g_assert (priv->tls_hop == NULL); priv->tls_hop = g_new0 (GoodixTlsServer, 1); - if (!priv->tls_ready_callback) - priv->tls_ready_callback = g_new0 (GoodixCallbackInfo, 1); - priv->tls_ready_callback->callback = G_CALLBACK (callback); - priv->tls_ready_callback->user_data = user_data; - GoodixTlsServer *s = priv->tls_hop; + g_clear_pointer (&priv->tls_ready_callback, g_free); + priv->tls_ready_callback = make_cb_info (G_CALLBACK (callback), user_data); + s = priv->tls_hop; s->user_data = self; - GError *err = NULL; if (!goodix_tls_server_init (priv->tls_hop, &err)) { fp_err ("failed to init tls server, error: %s, code: %d", @@ -1875,6 +1590,15 @@ goodix_tls_ready_image_handler (FpDevice *dev, guint8 *data, { GoodixCallbackInfo *cb_info = user_data; GoodixImageCallback callback = (GoodixImageCallback) cb_info->callback; + FpiDeviceGoodixTls *self; + FpiDeviceGoodixTlsPrivate *priv; + guint8 *tls_data; + guint16 tls_len; + int sent; + guint32 size; + guint8 *buff; + GError *err = NULL; + int read_size; if (error) { @@ -1882,12 +1606,11 @@ goodix_tls_ready_image_handler (FpDevice *dev, guint8 *data, g_free (cb_info); return; } - FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); - FpiDeviceGoodixTlsPrivate *priv = - fpi_device_goodixtls_get_instance_private (self); + self = FPI_DEVICE_GOODIXTLS (dev); + priv = fpi_device_goodixtls_get_instance_private (self); - guint8 *tls_data = data; - guint16 tls_len = length; + tls_data = data; + tls_len = length; if (length > 9 && data[0] == 0x00 && data[1] == 0x20) { @@ -1907,23 +1630,31 @@ goodix_tls_ready_image_handler (FpDevice *dev, guint8 *data, } } - goodix_tls_client_write (priv->tls_hop, tls_data, tls_len); + sent = goodix_tls_client_write (priv->tls_hop, tls_data, tls_len); - guint32 size = 65535; - guint8 *buff = malloc (size); - GError *err = NULL; - int read_size = goodix_tls_server_read (priv->tls_hop, buff, size, &err); + if (sent < 0) + { + GError *write_error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, + "failed to send data to tls server"); + callback (dev, NULL, 0, cb_info->user_data, write_error); + g_free (cb_info); + return; + } + + size = 65535; + buff = g_malloc (size); + read_size = goodix_tls_server_read (priv->tls_hop, buff, size, &err); if (read_size <= 0) { callback (dev, NULL, 0, cb_info->user_data, err); - free (buff); + g_free (buff); g_free (cb_info); return; } callback (dev, buff, read_size, cb_info->user_data, NULL); - free (buff); + g_free (buff); g_free (cb_info); } @@ -1931,11 +1662,10 @@ void goodix_tls_read_image (FpDevice *dev, GoodixImageCallback callback, gpointer user_data) { - g_assert (callback); - GoodixCallbackInfo *cb_info = g_new0 (GoodixCallbackInfo, 1); + GoodixCallbackInfo *cb_info; - cb_info->callback = G_CALLBACK (callback); - cb_info->user_data = user_data; + g_assert (callback); + cb_info = make_cb_info (G_CALLBACK (callback), user_data); goodix_send_mcu_get_image (dev, goodix_tls_ready_image_handler, cb_info); } @@ -1948,4 +1678,8 @@ fpi_device_goodixtls_init (FpiDeviceGoodixTls *self) static void fpi_device_goodixtls_class_init (FpiDeviceGoodixTlsClass *class) { + /* Generic GoodixDefault MCU_GET_IMAGE payload unless a subclass + * provides capture_payload/capture_payload_len. */ + class->capture_payload = NULL; + class->capture_payload_len = 0; } diff --git a/libfprint/drivers/goodixtls/goodix.h b/libfprint/drivers/goodixtls/goodix.h index 6512a97be..5f98eec5d 100644 --- a/libfprint/drivers/goodixtls/goodix.h +++ b/libfprint/drivers/goodixtls/goodix.h @@ -23,7 +23,7 @@ #include "drivers_api.h" -/* 1 second USB timeout */ +/* 1 second USB timeout, applied per USB chunk in goodix_send_data */ #define GOODIX_TIMEOUT (1000) /* NOP is a flush operation where silence from the MCU is expected. */ @@ -41,9 +41,15 @@ struct _FpiDeviceGoodixTlsClass gint interface; guint8 ep_in; guint8 ep_out; + + /* MCU_GET_IMAGE capture payload override. NULL/0 selects the generic + * GoodixDefault payload; subclasses with device-specific capture bytes + * (e.g. 5e0a) set both fields in class_init. */ + const guint8 *capture_payload; + guint16 capture_payload_len; }; -typedef struct __attribute__((__packed__)) _GoodixCallbackInfo +typedef struct _GoodixCallbackInfo { GCallback callback; gpointer user_data; @@ -106,83 +112,18 @@ gchar *data_to_str (guint8 *data, * @{ * */ -void goodix_receive_done (FpDevice *dev, - guint8 *data, - guint16 length, - GError *error); - -void goodix_receive_success (FpDevice *dev, - guint8 *data, - guint16 length, - gpointer user_data, - GError *error); - -void goodix_receive_reset (FpDevice *dev, - guint8 *data, - guint16 length, - gpointer user_data, - GError *error); - void goodix_receive_none (FpDevice *dev, guint8 *data, guint16 length, gpointer user_data, GError *error); -void goodix_receive_none_tolerant (FpDevice *dev, - guint8 *data, - guint16 length, - gpointer user_data, - GError *error); - void goodix_receive_default (FpDevice *dev, guint8 *data, guint16 length, gpointer user_data, GError *error); -void goodix_receive_preset_psk_read (FpDevice *dev, - guint8 *data, - guint16 length, - gpointer user_data, - GError *error); - -void goodix_receive_preset_psk_write (FpDevice *dev, - guint8 *data, - guint16 length, - gpointer user_data, - GError *error); - -void goodix_receive_ack (FpDevice *dev, - guint8 *data, - guint16 length, - gpointer user_data, - GError *error); - -void goodix_receive_firmware_version (FpDevice *dev, - guint8 *data, - guint16 length, - gpointer user_data, - GError *error); - -void goodix_receive_protocol (FpDevice *dev, - guint8 *data, - guint32 length); - -void goodix_receive_pack (FpDevice *dev, - guint8 *data, - guint32 length); - -void goodix_receive_data_cb (FpiUsbTransfer *transfer, - FpDevice *dev, - gpointer user_data, - GError *error); - -void goodix_receive_timeout_cb (FpDevice *dev, - gpointer user_data); - -void goodix_receive_data (FpDevice *dev); - /** @} */ /** @@ -193,42 +134,6 @@ void goodix_receive_data (FpDevice *dev); void goodix_start_read_loop (FpDevice *dev); void goodix_stop_read_loop (FpDevice *dev); -/** - * @brief Send raw data to the device over USB - * @note You should never need to call this directly from your driver! - * - * @param dev - * @param data data to be sent - * @param length length of the data - * @param free_func free function for the data or NULL - * @param error error output - * @return gboolean TRUE if successful, FALSE otherwise - */ -gboolean goodix_send_data (FpDevice *dev, - guint8 *data, - guint32 length, - GDestroyNotify free_func, - GError **error); - -/** - * @brief Send a single packet to the device - * @note You should never need to call this directly from your driver! - * - * @param dev - * @param flags - * @param payload - * @param length - * @param free_func - * @param error - * @return gboolean - */ -gboolean goodix_send_pack (FpDevice *dev, - guint8 flags, - guint8 *payload, - guint16 length, - GDestroyNotify free_func, - GError **error); - /** * @brief Low level function to send a protocol message to the device * @note You should never need to call this directly from your driver! @@ -266,18 +171,6 @@ void goodix_send_nop (FpDevice *dev, GoodixNoneCallback callback, gpointer user_data); -/** - * @brief Tell the device we want an image from it. The response will be TLS encrypted so you probably don't - * want to call this from your driver, checkout goodix_tls_read_image() if you want an image from the device - * - * @param dev - * @param callback - * @param user_data - */ -void goodix_send_mcu_get_image (FpDevice *dev, - GoodixImageCallback callback, - gpointer user_data); - /** * @brief Tell the device we want to wait for the user to present their finger * @@ -331,17 +224,6 @@ void goodix_send_nav_0 (FpDevice *dev, GoodixDefaultCallback callback, gpointer user_data); -void goodix_send_mcu_switch_to_idle_mode (FpDevice *dev, - guint8 sleep_time, - GoodixNoneCallback callback, - gpointer user_data); - -void goodix_send_write_sensor_register (FpDevice *dev, - guint16 address, - guint16 value, - GoodixNoneCallback callback, - gpointer user_data); - void goodix_send_read_sensor_register (FpDevice *dev, guint16 address, guint8 length, @@ -365,11 +247,6 @@ void goodix_send_upload_config_mcu (FpDevice *dev, GoodixSuccessCallback callback, gpointer user_data); -void goodix_send_set_powerdown_scan_frequency (FpDevice *dev, - guint16 powerdown_scan_frequency, - GoodixSuccessCallback callback, - gpointer user_data); - /** * @brief Turn the chip on * @@ -383,21 +260,6 @@ void goodix_send_enable_chip (FpDevice *dev, GoodixNoneCallback callback, gpointer user_data); -/** - * @brief Send a reset command to the device - * - * @param dev - * @param reset_sensor - * @param sleep_time - * @param callback - * @param user_data - */ -void goodix_send_reset (FpDevice *dev, - gboolean reset_sensor, - guint8 sleep_time, - GoodixResetCallback callback, - gpointer user_data); - /** * @brief Ask the device what firmware version it is running. Response is null-terminated string * @@ -420,64 +282,6 @@ void goodix_send_query_mcu_state (FpDevice *dev, GoodixNoneCallback callback, gpointer user_data); -/** - * @brief Tell the device we want to start talking TLS - * @note You probably don't need to call this from your driver directly, checkout the goodix_tls_* functions - * - * @param dev - * @param callback - * @param user_data - */ -void goodix_send_request_tls_connection (FpDevice *dev, - GoodixDefaultCallback callback, - gpointer user_data); - -/** - * @brief Tell the device that we have successfully established TLS communication with it - * @note You probably don't need to call this from your driver directly, checkout the goodix_tls_* functions - * - * @param dev - * @param callback - * @param user_data - */ -void goodix_send_tls_successfully_established (FpDevice *dev, - GoodixNoneCallback callback, - gpointer user_data); - -void goodix_send_set_drv_state (FpDevice *dev, - GoodixNoneCallback cb, - gpointer ud); - -void goodix_send_mcu_get_pov_image (FpDevice *dev, - GoodixDefaultCallback cb, - gpointer ud); - -void goodix_send_set_pov_config (FpDevice *dev, - const guint8 *cfg, - guint16 len, - GDestroyNotify ff, - GoodixNoneCallback cb, - gpointer ud); - -/** - * @brief Set the device preset psk. May not work for all device firmware versions - * - * @param dev - * @param flags - * @param psk - * @param length - * @param free_func - * @param callback - * @param user_data - */ -void goodix_send_preset_psk_write (FpDevice *dev, - guint32 flags, - guint8 *psk, - guint16 length, - GDestroyNotify free_func, - GoodixSuccessCallback callback, - gpointer user_data); - /** * @brief Ask the device what preset psk it has * @@ -583,18 +387,6 @@ void goodix_session_mark_clean (FpDevice *dev); void goodix_session_mark_dirty (FpDevice *dev); gboolean goodix_session_is_clean (FpDevice *dev); -/** - * @brief Read a TLS packet from the device - * @note You probably won't ever need to call this directly from your driver - * - * @param dev - * @param callback - * @param user_data - */ -void goodix_read_tls (FpDevice *dev, - GoodixTlsCallback callback, - gpointer user_data); - /** * @brief Initialise TLS with the device. Performs handshaking and such for you * diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c index 830009f51..4e5766c4c 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.c +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -87,6 +87,74 @@ G_DECLARE_FINAL_TYPE (FpiDeviceGoodixTls5e0a, fpi_device_goodixtls5e0a, FPI, G_DEFINE_TYPE (FpiDeviceGoodixTls5e0a, fpi_device_goodixtls5e0a, FPI_TYPE_DEVICE_GOODIXTLS5XX); +/* Windows capture ground truth tables (APP_10036, goodix-win.pcapng). + * File-static: only this TU uses them. */ +#define GOODIX_CMD_SESSION_D6 (0xd6) + +/* Static host TLS PSK for TLS_PSK_WITH_AES_128_CBC_SHA256, observed in passive + * USB captures of the Windows driver traffic. Activation uses it directly: + * the 0xe4-readable slot reports factory bytes (not the TLS key) and 0xe0 + * writes are rejected, so there is no on-device provisioning. Per-unit scope + * of this key is unconfirmed; see PR description. */ +static const guint8 goodix_5e0a_psk[32] = { + 0xd8, 0x53, 0xad, 0x19, 0x41, 0xb2, 0xdc, 0x53, + 0x50, 0xc7, 0x66, 0xcd, 0x72, 0x6e, 0xf7, 0xa5, + 0xdf, 0x7d, 0x5f, 0xa3, 0x90, 0x53, 0xbf, 0xac, + 0x26, 0x9c, 0xe7, 0x52, 0xd7, 0xa8, 0xb2, 0xab +}; + +/* ChicagoH GF3658 DN3 Configuration (256 bytes, wbdi.dll offset 0x197c50, checksum 0x0e53) */ +static const guint8 goodix_5e0a_config[256] = { + 0xb0, 0x11, 0x60, 0x71, 0x2c, 0x9d, 0x2c, 0xc9, 0x1c, 0xe5, 0x18, 0xfd, 0x00, 0xfd, 0x00, 0xfd, + 0x03, 0xba, 0x00, 0x01, 0x80, 0xca, 0x00, 0x04, 0x00, 0x84, 0x00, 0x15, 0xb3, 0x86, 0x00, 0x00, + 0xc4, 0x88, 0x00, 0x00, 0xba, 0x8a, 0x00, 0x00, 0xb2, 0x8c, 0x00, 0x00, 0xaa, 0x8e, 0x00, 0x00, + 0xc1, 0x90, 0x00, 0xbb, 0xbb, 0x92, 0x00, 0xb1, 0xb1, 0x94, 0x00, 0x00, 0xa8, 0x96, 0x00, 0x00, + 0xb6, 0x98, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, + 0x00, 0xd6, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x01, 0x05, 0xd0, 0x00, 0x00, + 0x00, 0x70, 0x00, 0x00, 0x00, 0x72, 0x00, 0x78, 0x56, 0x74, 0x00, 0x34, 0x12, 0x20, 0x00, 0x10, + 0x40, 0x2a, 0x01, 0x02, 0x04, 0x22, 0x00, 0x01, 0x20, 0x24, 0x00, 0x32, 0x00, 0x80, 0x00, 0x01, + 0x00, 0x5c, 0x00, 0x80, 0x00, 0x56, 0x00, 0x24, 0x20, 0x58, 0x00, 0x03, 0x02, 0x32, 0x00, 0x0c, + 0x02, 0x66, 0x00, 0x03, 0x00, 0x7c, 0x00, 0x00, 0x58, 0x82, 0x00, 0x80, 0x15, 0x2a, 0x01, 0x82, + 0x03, 0x22, 0x00, 0x01, 0x20, 0x24, 0x00, 0x14, 0x00, 0x80, 0x00, 0x01, 0x00, 0x5c, 0x00, 0x00, + 0x01, 0x56, 0x00, 0x04, 0x20, 0x58, 0x00, 0x03, 0x02, 0x32, 0x00, 0x0c, 0x02, 0x66, 0x00, 0x03, + 0x00, 0x7c, 0x00, 0x00, 0x58, 0x82, 0x00, 0x80, 0x15, 0x2a, 0x01, 0x08, 0x00, 0x5c, 0x00, 0x80, + 0x00, 0x54, 0x00, 0x10, 0x01, 0x62, 0x00, 0x04, 0x03, 0x64, 0x00, 0x19, 0x00, 0x66, 0x00, 0x03, + 0x00, 0x7c, 0x00, 0x01, 0x58, 0x2a, 0x01, 0x08, 0x00, 0x5c, 0x00, 0x00, 0x01, 0x52, 0x00, 0x08, + 0x00, 0x54, 0x00, 0x00, 0x01, 0x66, 0x00, 0x03, 0x00, 0x7c, 0x00, 0x01, 0x58, 0x00, 0x53, 0x0e +}; + +/* Session initialization commands */ +static const guint8 goodix_5e0a_query_ae[3] = {0x00, 0x01, 0x00}; +static const guint8 goodix_5e0a_session_d6[2] = {0x00, 0x00}; + +/* Exact 10-byte image capture payload: 05 00 b0 00 b2 00 b0 00 b1 00 (37/37 identical in capture). + * Also published to the base class via capture_payload/capture_payload_len. */ +static const guint8 goodix_5e0a_img_payload[10] = { + 0x05, 0x00, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00 +}; + +/* Exact 35-byte steady-state DOWN table S12 (pkts 302/328/406/432/792): + * 1c 01 b0 00 b2 00 b0 00 b1 00 + slots [80 b7 80 ce 80 aa 80 be 80 b1 80 c2] + 00 00 00 00 + b0 00 b2 00 b0 00 b1 00 00 */ +static const guint8 goodix_5e0a_down_s12[35] = { + 0x1c, 0x01, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, + 0x80, 0xb7, 0x80, 0xce, 0x80, 0xaa, 0x80, 0xbe, 0x80, 0xb1, 0x80, 0xc2, + 0x00, 0x00, 0x00, 0x00, + 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, 0x00 +}; + +/* Exact 35-byte steady-state UP table U01 (pkts 42/50): + * 0e 01 b0 00 b2 00 b0 00 b1 00 + slots [80 94 80 c2 80 97 80 b1 80 a5 80 21] + 13 zeros */ +static const guint8 goodix_5e0a_up_u01[35] = { + 0x0e, 0x01, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, + 0x80, 0x94, 0x80, 0xc2, 0x80, 0x97, 0x80, 0xb1, 0x80, 0xa5, 0x80, 0x21, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const FpIdEntry goodix_5e0a_id_table[] = { + {.vid = 0x27c6, .pid = 0x5e0a}, + {.vid = 0, .pid = 0, .driver_data = 0}, +}; + static void goodix5e0a_reset_touch_frames (FpiDeviceGoodixTls5e0a *self); #define GOODIX_5E0A_TLS_PARK_TTL_US (G_USEC_PER_SEC * 30) @@ -572,6 +640,8 @@ static void goodix5e0a_on_d6_reply (FpDevice *dev, guint8 *data, guint16 len, gpointer ssm, GError *err) { + FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + if (err) { fp_warn ("5e0a session d6 reply error: %s", err->message); @@ -581,7 +651,6 @@ goodix5e0a_on_d6_reply (FpDevice *dev, guint8 *data, guint16 len, { fp_dbg ("5e0a session d6 replied successfully (len=%u)", len); } - FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); self->session_started = TRUE; if (self->retry_guard) fpi_ssm_jump_to_state (ssm, SCAN_5E0A_FDT_UP_1); @@ -599,10 +668,10 @@ static void goodix5e0a_on_down_poll_timeout (FpDevice *dev, gpointer user_data) { FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + FpiSsm *ssm = user_data; self->down_timeout = NULL; - FpiSsm *ssm = user_data; if (self->scan_ssm != ssm) return; if (self->scan_timeout_gen != self->scan_gen) @@ -618,6 +687,10 @@ goodix5e0a_on_fdt_down_reply (FpDevice *dev, guint8 *data, guint16 len, gpointer ssm, GError *err) { FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); + guint8 status; + GString *hex_str; + guint32 channel_energy = 0; + gboolean touch; if (err) { @@ -630,21 +703,20 @@ goodix5e0a_on_fdt_down_reply (FpDevice *dev, guint8 *data, guint16 len, return; } - guint8 status = (len > 0) ? data[0] : 0x00; + status = (len > 0) ? data[0] : 0x00; - GString *hex_str = g_string_new (""); + hex_str = g_string_new (""); for (guint16 i = 0; i < len; i++) g_string_append_printf (hex_str, "%02x ", data[i]); fp_dbg ("5e0a D32 reply: status=0x%02x len=%u bytes=[%s]", status, len, hex_str->str); g_string_free (hex_str, TRUE); - guint32 channel_energy = 0; if (len >= 4) for (guint16 i = 4; i + 1 < len; i += 2) channel_energy += (guint32) data[i] | ((guint32) data[i + 1] << 8); /* Gating rule: touch = channel-byte energy (data[2] != 0xff and channel_energy > 0), never byte0 */ - gboolean touch = (len >= 4 && data[2] != 0xff && channel_energy > 0); + touch = (len >= 4 && data[2] != 0xff && channel_energy > 0); if (touch) { @@ -676,11 +748,14 @@ static guint goodix5e0a_count_minutiae (FpImage *img); static guint32 goodix5e0a_decode_frame (GoodixTls5xxPix *out_row_major, const guint8 *data, guint16 len) { + g_autofree guint8 *packed = NULL; + guint32 packed_len = 0; + guint32 pixel_idx = 0; + if (!out_row_major || !data) return 0; - g_autofree guint8 *packed = g_new0 (guint8, GOODIX_5E0A_ACT_BYTES); - guint32 packed_len = 0; + packed = g_new0 (guint8, GOODIX_5E0A_ACT_BYTES); /* A canonical ChicagoH frame is 80 blocks of 132 bytes followed by a * four-byte footer. Each block carries 96 packed pixel bytes and 36 zero @@ -697,7 +772,6 @@ goodix5e0a_decode_frame (GoodixTls5xxPix *out_row_major, const guint8 *data, gui packed_len += GOODIX_5E0A_BLOCK_ACTIVE_BYTES; } - guint32 pixel_idx = 0; for (guint32 i = 0; i + 6 <= packed_len && pixel_idx + 4 <= GOODIX_5E0A_FRAME_SIZE; i += 6) { const guint8 *c = packed + i; @@ -752,6 +826,13 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, FpiDeviceAction action = fpi_device_get_current_action (dev); g_autofree GoodixTls5xxPix *raw_frame = NULL; FpImage *img; + guint32 padding_nonzero = 0; + guint32 decoded_pixels = 0; + guint total_nonzero = 0; + guint16 raw_min = 65535, raw_max = 0; + guint frame_active = 0; + guint16 frame_min = 65535, frame_max = 0; + guint frame_range = 0; if (self->scan_ssm != ssm) { @@ -794,7 +875,6 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]); } - guint32 padding_nonzero = 0; if (data) { for (guint32 block = 0; block < GOODIX_5E0A_FRAME_BLOCKS; block++) @@ -808,12 +888,8 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, } raw_frame = g_new0 (GoodixTls5xxPix, GOODIX_5E0A_FRAME_SIZE); - guint32 decoded_pixels = goodix5e0a_decode_frame (raw_frame, data, len); + decoded_pixels = goodix5e0a_decode_frame (raw_frame, data, len); - guint total_nonzero = 0; - guint16 raw_min = 65535, raw_max = 0; - guint frame_active = 0; - guint16 frame_min = 65535, frame_max = 0; for (guint32 i = 0; i < GOODIX_5E0A_FRAME_SIZE; i++) { if (raw_frame[i] > 0) @@ -833,8 +909,8 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, frame_max = raw_frame[i]; } } - guint frame_range = (frame_min != 65535 && frame_max > frame_min) - ? (guint) (frame_max - frame_min) : 0; + frame_range = (frame_min != 65535 && frame_max > frame_min) + ? (guint) (frame_max - frame_min) : 0; fp_dbg ("5e0a wire layout: decoded_px=%u blocks=%u active_bytes=%u padding_nonzero=%u footer_bytes=%u", decoded_pixels, MIN ((guint32) len / GOODIX_5E0A_BLOCK_BYTES, (guint32) GOODIX_5E0A_FRAME_BLOCKS), @@ -848,6 +924,8 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, if (action == FPI_DEVICE_ACTION_ENROLL) { + guint minutiae_count; + if (img == NULL) { fp_dbg ("5e0a enrollment touch rejected: poor frame quality (press firmer)"); @@ -855,7 +933,7 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, fpi_ssm_next_state (ssm); return; } - guint minutiae_count = goodix5e0a_count_minutiae (img); + minutiae_count = goodix5e0a_count_minutiae (img); fp_dbg ("5e0a enrollment quality check: minutiae_count=%u (floor=%d)", minutiae_count, GOODIX_5E0A_ENROLL_MIN_MINUTIAE); if (minutiae_count < GOODIX_5E0A_ENROLL_MIN_MINUTIAE) @@ -1125,6 +1203,7 @@ goodix5e0a_deactivate (FpImageDevice *img_dev) FpDevice *dev = FP_DEVICE (img_dev); FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); gboolean scan_was_active; + g_autoptr(GError) tls_err = NULL; goodix5e0a_reset_touch_frames (self); goodix_activation_gen_bump (dev); @@ -1165,7 +1244,6 @@ goodix5e0a_deactivate (FpImageDevice *img_dev) self->tls_parked = FALSE; goodix_session_mark_dirty (dev); - g_autoptr(GError) tls_err = NULL; goodix_shutdown_tls (dev, &tls_err); goodix_stop_read_loop (dev); fpi_image_device_deactivate_complete (img_dev, g_steal_pointer (&tls_err)); @@ -1205,6 +1283,9 @@ goodix5e0a_axis_correlation (const GoodixTls5xxPix *pix, { double sum_a = 0.0, sum_b = 0.0; guint count = 0; + double mean_a, mean_b; + double covariance = 0.0, variance_a = 0.0, variance_b = 0.0; + double denominator; for (int y = 0; y + dy < height; y++) for (int x = 0; x + dx < width; x++) @@ -1217,9 +1298,8 @@ goodix5e0a_axis_correlation (const GoodixTls5xxPix *pix, if (count == 0) return 0.0; - double mean_a = sum_a / count; - double mean_b = sum_b / count; - double covariance = 0.0, variance_a = 0.0, variance_b = 0.0; + mean_a = sum_a / count; + mean_b = sum_b / count; for (int y = 0; y + dy < height; y++) for (int x = 0; x + dx < width; x++) @@ -1231,7 +1311,7 @@ goodix5e0a_axis_correlation (const GoodixTls5xxPix *pix, variance_b += b * b; } - double denominator = sqrt (variance_a * variance_b); + denominator = sqrt (variance_a * variance_b); return denominator > 1e-6 ? covariance / denominator : 0.0; } @@ -1245,6 +1325,15 @@ process_raw_frame (GoodixTls5xxPix * pix) guint16 min_v = 65535, max_v = 0; guint active = 0; + guint16 range = 0; + double horizontal_corr = 0.0, vertical_corr = 0.0, horizontal_lag4_corr = 0.0; + GString *active_cols = NULL; + g_autofree float *residual = NULL; + float residual_min = G_MAXFLOAT; + float residual_max = -G_MAXFLOAT; + float residual_range = 0.0f; + g_autofree guint8 *normalized = NULL; + FpImage *scaled = NULL; for (int r = 0; r < H; ++r) { @@ -1264,13 +1353,13 @@ process_raw_frame (GoodixTls5xxPix * pix) if (min_v == 65535) min_v = 0; - guint16 range = (max_v > min_v) ? (max_v - min_v) : 1; + range = (max_v > min_v) ? (max_v - min_v) : 1; - double horizontal_corr = goodix5e0a_axis_correlation (pix, W, H, 1, 0); - double vertical_corr = goodix5e0a_axis_correlation (pix, W, H, 0, 1); - double horizontal_lag4_corr = goodix5e0a_axis_correlation (pix, W, H, 4, 0); + horizontal_corr = goodix5e0a_axis_correlation (pix, W, H, 1, 0); + vertical_corr = goodix5e0a_axis_correlation (pix, W, H, 0, 1); + horizontal_lag4_corr = goodix5e0a_axis_correlation (pix, W, H, 4, 0); - GString *active_cols = g_string_new (""); + active_cols = g_string_new (""); for (int c = 0; c < W; ++c) { guint32 c_sum = 0; @@ -1295,15 +1384,14 @@ process_raw_frame (GoodixTls5xxPix * pix) /* Remove the slowly varying pressure/offset field before global scaling. * A 3x3 local mean is the smallest window that removes this field without * averaging across a full ridge period. */ - g_autofree float *residual = g_new (float, GOODIX_5E0A_FRAME_SIZE); - float residual_min = G_MAXFLOAT; - float residual_max = -G_MAXFLOAT; + residual = g_new (float, GOODIX_5E0A_FRAME_SIZE); for (int y = 0; y < H; y++) { for (int x = 0; x < W; x++) { guint32 local_sum = 0; guint local_count = 0; + float value; for (int yy = MAX (0, y - 1); yy <= MIN (H - 1, y + 1); yy++) for (int xx = MAX (0, x - 1); xx <= MIN (W - 1, x + 1); xx++) { @@ -1311,20 +1399,20 @@ process_raw_frame (GoodixTls5xxPix * pix) local_count++; } - float value = pix[y * W + x] - (float) local_sum / local_count; + value = pix[y * W + x] - (float) local_sum / local_count; residual[y * W + x] = value; residual_min = MIN (residual_min, value); residual_max = MAX (residual_max, value); } } - float residual_range = residual_max - residual_min; + residual_range = residual_max - residual_min; fp_dbg ("5e0a local contrast: min=%.2f max=%.2f range=%.2f window=3x3 gain=%.2f", residual_min, residual_max, residual_range, GOODIX_5E0A_CONTRAST_GAIN); if (residual_range < 1.0f) return NULL; - g_autofree guint8 *normalized = g_new (guint8, GOODIX_5E0A_FRAME_SIZE); + normalized = g_new (guint8, GOODIX_5E0A_FRAME_SIZE); for (guint i = 0; i < GOODIX_5E0A_FRAME_SIZE; i++) { int value = (int) roundf (128.0f + residual[i] * GOODIX_5E0A_CONTRAST_GAIN); @@ -1334,32 +1422,39 @@ process_raw_frame (GoodixTls5xxPix * pix) /* Create the scaled 128x160 image directly via bilinear upscaling. * Use FPI_IMAGE_COLORS_INVERTED for capacitive ridges (high ADC = black). * Omit FPI_IMAGE_PARTIAL so remove_perimeter_pts=0 retains edge minutiae. */ - FpImage *scaled = fp_image_new (dst_w, dst_h); + scaled = fp_image_new (dst_w, dst_h); scaled->flags = FPI_IMAGE_COLORS_INVERTED; scaled->ppmm = 500.0 / 25.4; for (int y = 0; y < dst_h; y++) { float src_y = (y + 0.5f) * 0.5f - 0.5f; + int y0, y1; + float y_frac; + if (src_y < 0.0f) src_y = 0.0f; - int y0 = (int) src_y; - int y1 = (y0 + 1 < H) ? y0 + 1 : y0; - float y_frac = src_y - (float) y0; + y0 = (int) src_y; + y1 = (y0 + 1 < H) ? y0 + 1 : y0; + y_frac = src_y - (float) y0; for (int x = 0; x < dst_w; x++) { float src_x = (x + 0.5f) * 0.5f - 0.5f; + int x0, x1; + float x_frac, top, bot, val; + int norm; + if (src_x < 0.0f) src_x = 0.0f; - int x0 = (int) src_x; - int x1 = (x0 + 1 < W) ? x0 + 1 : x0; - float x_frac = src_x - (float) x0; - - float top = (float) normalized[y0 * W + x0] * (1.0f - x_frac) + (float) normalized[y0 * W + x1] * x_frac; - float bot = (float) normalized[y1 * W + x0] * (1.0f - x_frac) + (float) normalized[y1 * W + x1] * x_frac; - float val = top * (1.0f - y_frac) + bot * y_frac; - int norm = (int) roundf (val); + x0 = (int) src_x; + x1 = (x0 + 1 < W) ? x0 + 1 : x0; + x_frac = src_x - (float) x0; + + top = (float) normalized[y0 * W + x0] * (1.0f - x_frac) + (float) normalized[y0 * W + x1] * x_frac; + bot = (float) normalized[y1 * W + x0] * (1.0f - x_frac) + (float) normalized[y1 * W + x1] * x_frac; + val = top * (1.0f - y_frac) + bot * y_frac; + norm = (int) roundf (val); scaled->data[y * dst_w + x] = (guint8) CLAMP (norm, 0, 255); } } @@ -1372,30 +1467,35 @@ process_raw_frame (GoodixTls5xxPix * pix) static guint goodix5e0a_count_minutiae (FpImage *img) { + int w, h; + unsigned char *buf; + LFSPARMS parms = g_lfsparms_V2; + double ppmm; + MINUTIAE *minutiae = NULL; + int *qmap = NULL, *dmap = NULL, *lcmap = NULL, *lfmap = NULL, *hcmap = NULL; + int mw, mh, bw, bh, bd; + unsigned char *bdata = NULL; + int ret; + guint count; + if (!img || !img->data) return 0; - int w = img->width; - int h = img->height; - unsigned char *buf = g_memdup2 (img->data, w * h); + w = img->width; + h = img->height; + buf = g_memdup2 (img->data, w * h); if (img->flags & FPI_IMAGE_COLORS_INVERTED) for (int i = 0; i < w * h; i++) buf[i] = 255 - buf[i]; - LFSPARMS parms = g_lfsparms_V2; parms.remove_perimeter_pts = 0; - double ppmm = img->ppmm > 0 ? img->ppmm : (500.0 / 25.4); + ppmm = img->ppmm > 0 ? img->ppmm : (500.0 / 25.4); - MINUTIAE *minutiae = NULL; - int *qmap = NULL, *dmap = NULL, *lcmap = NULL, *lfmap = NULL, *hcmap = NULL; - int mw, mh, bw, bh, bd; - unsigned char *bdata = NULL; - - int ret = get_minutiae (&minutiae, &qmap, &dmap, &lcmap, &lfmap, &hcmap, - &mw, &mh, &bdata, &bw, &bh, &bd, - buf, w, h, 8, ppmm, &parms); - guint count = (ret == 0 && minutiae) ? minutiae->num : 0; + ret = get_minutiae (&minutiae, &qmap, &dmap, &lcmap, &lfmap, &hcmap, + &mw, &mh, &bdata, &bw, &bh, &bd, + buf, w, h, 8, ppmm, &parms); + count = (ret == 0 && minutiae) ? minutiae->num : 0; g_free (buf); if (minutiae) @@ -1416,11 +1516,12 @@ goodix5e0a_count_minutiae (FpImage *img) return count; } -void +static void goodix5e0a_suspend (FpDevice *dev) { FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); FpiDeviceAction action = fpi_device_get_current_action (dev); + g_autoptr(GError) tls_err = NULL; fp_dbg ("5e0a suspend requested during action: %d", action); @@ -1456,15 +1557,22 @@ goodix5e0a_suspend (FpDevice *dev) /* Terminate background read loop and cancel transfers */ goodix_stop_read_loop (dev); - /* Tear down TLS context */ - goodix_shutdown_tls (dev, NULL); + /* Tear down TLS context. Synchronous (joins the serve thread); log but + * do not propagate failures so the NOT_SUPPORTED suspend completion below + * still triggers clean core deactivation. */ + if (!goodix_shutdown_tls (dev, &tls_err)) + { + fp_warn ("5e0a suspend: TLS shutdown failed: %s", + tls_err ? tls_err->message : "unknown error"); + g_clear_error (&tls_err); + } /* Complete suspend with NOT_SUPPORTED to trigger clean core deactivation * of the interactive task, releasing PAM claims before sleep. */ fpi_device_suspend_complete (dev, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); } -void +static void goodix5e0a_resume (FpDevice *dev) { fp_dbg ("5e0a resume requested"); @@ -1495,6 +1603,8 @@ fpi_device_goodixtls5e0a_class_init (FpiDeviceGoodixTls5e0aClass * class) gx_class->interface = GOODIX_5E0A_INTERFACE; gx_class->ep_in = GOODIX_5E0A_EP_IN; gx_class->ep_out = GOODIX_5E0A_EP_OUT; + gx_class->capture_payload = goodix_5e0a_img_payload; + gx_class->capture_payload_len = sizeof (goodix_5e0a_img_payload); dev_class->id = "goodixtls5e0a"; dev_class->full_name = "Goodix TLS Fingerprint Sensor 5e0a"; diff --git a/libfprint/drivers/goodixtls/goodix5e0a.h b/libfprint/drivers/goodixtls/goodix5e0a.h index 33857b5c1..ebed6e22c 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.h +++ b/libfprint/drivers/goodixtls/goodix5e0a.h @@ -53,80 +53,4 @@ #define GOODIX_5E0A_REG_GAIN_EXPOSURE_RESET_VAL (0x020a) /* Little-endian 16-bit: \x0a\x02 */ -/* Static host TLS PSK for TLS_PSK_WITH_AES_128_CBC_SHA256, observed in passive - * USB captures of the Windows driver traffic. Activation uses it directly: - * the 0xe4-readable slot reports factory bytes (not the TLS key) and 0xe0 - * writes are rejected, so there is no on-device provisioning. Per-unit scope - * of this key is unconfirmed; see PR description. */ -static const guint8 goodix_5e0a_psk[] = { - 0xd8, 0x53, 0xad, 0x19, 0x41, 0xb2, 0xdc, 0x53, - 0x50, 0xc7, 0x66, 0xcd, 0x72, 0x6e, 0xf7, 0xa5, - 0xdf, 0x7d, 0x5f, 0xa3, 0x90, 0x53, 0xbf, 0xac, - 0x26, 0x9c, 0xe7, 0x52, 0xd7, 0xa8, 0xb2, 0xab -}; - -/* ChicagoH GF3658 DN3 Configuration (256 bytes, wbdi.dll offset 0x197c50, checksum 0x0e53) */ -static const guint8 goodix_5e0a_config[256] = { - 0xb0, 0x11, 0x60, 0x71, 0x2c, 0x9d, 0x2c, 0xc9, 0x1c, 0xe5, 0x18, 0xfd, 0x00, 0xfd, 0x00, 0xfd, - 0x03, 0xba, 0x00, 0x01, 0x80, 0xca, 0x00, 0x04, 0x00, 0x84, 0x00, 0x15, 0xb3, 0x86, 0x00, 0x00, - 0xc4, 0x88, 0x00, 0x00, 0xba, 0x8a, 0x00, 0x00, 0xb2, 0x8c, 0x00, 0x00, 0xaa, 0x8e, 0x00, 0x00, - 0xc1, 0x90, 0x00, 0xbb, 0xbb, 0x92, 0x00, 0xb1, 0xb1, 0x94, 0x00, 0x00, 0xa8, 0x96, 0x00, 0x00, - 0xb6, 0x98, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, - 0x00, 0xd6, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x01, 0x05, 0xd0, 0x00, 0x00, - 0x00, 0x70, 0x00, 0x00, 0x00, 0x72, 0x00, 0x78, 0x56, 0x74, 0x00, 0x34, 0x12, 0x20, 0x00, 0x10, - 0x40, 0x2a, 0x01, 0x02, 0x04, 0x22, 0x00, 0x01, 0x20, 0x24, 0x00, 0x32, 0x00, 0x80, 0x00, 0x01, - 0x00, 0x5c, 0x00, 0x80, 0x00, 0x56, 0x00, 0x24, 0x20, 0x58, 0x00, 0x03, 0x02, 0x32, 0x00, 0x0c, - 0x02, 0x66, 0x00, 0x03, 0x00, 0x7c, 0x00, 0x00, 0x58, 0x82, 0x00, 0x80, 0x15, 0x2a, 0x01, 0x82, - 0x03, 0x22, 0x00, 0x01, 0x20, 0x24, 0x00, 0x14, 0x00, 0x80, 0x00, 0x01, 0x00, 0x5c, 0x00, 0x00, - 0x01, 0x56, 0x00, 0x04, 0x20, 0x58, 0x00, 0x03, 0x02, 0x32, 0x00, 0x0c, 0x02, 0x66, 0x00, 0x03, - 0x00, 0x7c, 0x00, 0x00, 0x58, 0x82, 0x00, 0x80, 0x15, 0x2a, 0x01, 0x08, 0x00, 0x5c, 0x00, 0x80, - 0x00, 0x54, 0x00, 0x10, 0x01, 0x62, 0x00, 0x04, 0x03, 0x64, 0x00, 0x19, 0x00, 0x66, 0x00, 0x03, - 0x00, 0x7c, 0x00, 0x01, 0x58, 0x2a, 0x01, 0x08, 0x00, 0x5c, 0x00, 0x00, 0x01, 0x52, 0x00, 0x08, - 0x00, 0x54, 0x00, 0x00, 0x01, 0x66, 0x00, 0x03, 0x00, 0x7c, 0x00, 0x01, 0x58, 0x00, 0x53, 0x0e -}; - -/* Windows capture ground truth tables (APP_10036, goodix-win.pcapng) */ -#define GOODIX_CMD_SESSION_D6 (0xd6) - -/* Session initialization commands */ -static const guint8 goodix_5e0a_query_ae[3] = {0x00, 0x01, 0x00}; -static const guint8 goodix_5e0a_session_d6[2] = {0x00, 0x00}; - -/* Exact 10-byte image capture payload: 05 00 b0 00 b2 00 b0 00 b1 00 (37/37 identical in capture) */ -static const guint8 goodix_5e0a_img_payload[10] = { - 0x05, 0x00, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00 -}; - -/* Exact 35-byte steady-state DOWN table S12 (pkts 302/328/406/432/792): - * 1c 01 b0 00 b2 00 b0 00 b1 00 + slots [80 b7 80 ce 80 aa 80 be 80 b1 80 c2] + 00 00 00 00 + b0 00 b2 00 b0 00 b1 00 00 */ -static const guint8 goodix_5e0a_down_s12[35] = { - 0x1c, 0x01, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, - 0x80, 0xb7, 0x80, 0xce, 0x80, 0xaa, 0x80, 0xbe, 0x80, 0xb1, 0x80, 0xc2, - 0x00, 0x00, 0x00, 0x00, - 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, 0x00 -}; - -/* Exact 35-byte DOWN retry table (pkt 114, sent after 0x80 poor contact status): - * 1c 01 b0 00 b2 00 b0 00 b1 00 + slots [80 b6 80 ce 80 aa 80 be 80 b2 80 c2] + 00 00 00 00 + b0 00 b2 00 b0 00 b1 00 00 */ -static const guint8 goodix_5e0a_down_retry[35] = { - 0x1c, 0x01, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, - 0x80, 0xb6, 0x80, 0xce, 0x80, 0xaa, 0x80, 0xbe, 0x80, 0xb2, 0x80, 0xc2, - 0x00, 0x00, 0x00, 0x00, - 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, 0x00 -}; - -/* Exact 35-byte steady-state UP table U01 (pkts 42/50): - * 0e 01 b0 00 b2 00 b0 00 b1 00 + slots [80 94 80 c2 80 97 80 b1 80 a5 80 21] + 13 zeros */ -static const guint8 goodix_5e0a_up_u01[35] = { - 0x0e, 0x01, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, - 0x80, 0x94, 0x80, 0xc2, 0x80, 0x97, 0x80, 0xb1, 0x80, 0xa5, 0x80, 0x21, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 -}; - -static const FpIdEntry goodix_5e0a_id_table[] = { - {.vid = 0x27c6, .pid = 0x5e0a}, - {.vid = 0, .pid = 0, .driver_data = 0}, -}; - -void goodix5e0a_suspend (FpDevice *dev); -void goodix5e0a_resume (FpDevice *dev); +/* Wire tables and PSK live in goodix5e0a.c as file-static consts. */ diff --git a/libfprint/drivers/goodixtls/goodix5xx.c b/libfprint/drivers/goodixtls/goodix5xx.c index b2680d1cb..65fc35546 100644 --- a/libfprint/drivers/goodixtls/goodix5xx.c +++ b/libfprint/drivers/goodixtls/goodix5xx.c @@ -61,38 +61,47 @@ enum SCAN_STAGES { }; +/* Base class provides no default MCU config; 5e0a overrides change_state + * so this guard only covers direct base-class scans. */ +static gboolean +get_mcu_cfg_or_skip (FpDevice *dev, FpiSsm *ssm, GoodixTls5xxMcuConfig *cfg) +{ + FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); + if (!cls->get_mcu_cfg) + { + fpi_ssm_next_state (ssm); + return FALSE; + } + *cfg = cls->get_mcu_cfg (); + return TRUE; +} + static void -send_switch_mode (FpDevice * dev, gpointer ssm, void (*mode_switch)(FpDevice *, +send_switch_mode (FpDevice * dev, FpiSsm * ssm, void (*mode_switch)(FpDevice *, const guint8 *, guint16, GDestroyNotify, GoodixDefaultCallback, gpointer)) { - FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); - /* No subclass provides get_mcu_cfg without also overriding change_state - * (5e0a does), but dereferencing it unguarded would NULL-crash any future - * base-scan user — same guard shape as the sibling FDT branches. */ - if (!cls->get_mcu_cfg) - { - fpi_ssm_next_state (ssm); - return; - } - GoodixTls5xxMcuConfig cfg = cls->get_mcu_cfg (); + GoodixTls5xxMcuConfig cfg; + if (!get_mcu_cfg_or_skip (dev, ssm, &cfg)) + return; mode_switch (dev, cfg.data, cfg.data_len, cfg.free_fn, goodixtls5xx_check_none_cmd, ssm); } static void on_calibrate_scan (FpDevice * dev, guint8 * data, guint16 len, gpointer ssm, GError * err) { + FpiDeviceGoodixTls5xx * self = FPI_DEVICE_GOODIXTLS5XX (dev); + FpiDeviceGoodixTls5xxPrivate * priv = fpi_device_goodixtls5xx_get_instance_private (self); + FpiDeviceGoodixTls5xxClass * cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (self); + if (err) { fpi_ssm_mark_failed (ssm, err); return; } - FpiDeviceGoodixTls5xx * self = FPI_DEVICE_GOODIXTLS5XX (dev); - FpiDeviceGoodixTls5xxPrivate * priv = fpi_device_goodixtls5xx_get_instance_private (self); - FpiDeviceGoodixTls5xxClass * cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (self); if (!priv->calibration_img) priv->calibration_img = g_try_new0 (GoodixTls5xxPix, cls->scan_height * cls->scan_width); if (!priv->calibration_img) @@ -168,6 +177,8 @@ void goodixtls5xx_check_firmware_version (FpDevice *dev, gchar *firmware, gpointer user_data, GError *error) { + FpiDeviceGoodixTls5xxClass * cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (FPI_DEVICE_GOODIXTLS5XX (dev)); + if (error) { fpi_ssm_mark_failed (user_data, error); @@ -175,7 +186,6 @@ goodixtls5xx_check_firmware_version (FpDevice *dev, gchar *firmware, } fp_dbg ("Device firmware: \"%s\"", firmware); - FpiDeviceGoodixTls5xxClass * cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (FPI_DEVICE_GOODIXTLS5XX (dev)); if (strcmp (firmware, cls->firmware_version)) { @@ -195,6 +205,7 @@ goodixtls5xx_check_preset_psk_read (FpDevice *dev, gboolean success, gpointer user_data, GError *error) { g_autofree gchar *psk_str = data_to_str (psk, length); + FpiDeviceGoodixTls5xxClass * cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); if (error) { @@ -213,8 +224,6 @@ goodixtls5xx_check_preset_psk_read (FpDevice *dev, gboolean success, fp_dbg ("Device PSK: 0x%s", psk_str); fp_dbg ("Device PSK flags: 0x%08x", flags); - FpiDeviceGoodixTls5xxClass * cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); - if (flags != cls->psk_flags) { g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, @@ -276,6 +285,8 @@ void goodixtls5xx_check_reset (FpDevice *dev, gboolean success, guint16 number, gpointer user_data, GError *error) { + FpiDeviceGoodixTls5xxClass * cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); + if (error) { fpi_ssm_mark_failed (user_data, error); @@ -292,7 +303,6 @@ goodixtls5xx_check_reset (FpDevice *dev, gboolean success, guint16 number, fp_dbg ("Device reset number: %d", number); - FpiDeviceGoodixTls5xxClass * cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); if (number != cls->reset_number) { g_set_error (&error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, @@ -359,19 +369,20 @@ static void scan_on_read_img (FpDevice *dev, guint8 *data, guint16 len, gpointer ssm, GError *err) { + FpImageDevice * img_dev = FP_IMAGE_DEVICE (dev); + FpiDeviceGoodixTls5xx * self = FPI_DEVICE_GOODIXTLS5XX (dev); + FpiDeviceGoodixTls5xxPrivate * priv = fpi_device_goodixtls5xx_get_instance_private (self); + FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); + GoodixTls5xxPix * raw_frame; + FpImage * img = NULL; + if (err) { fpi_ssm_mark_failed (ssm, err); return; } - FpImageDevice * img_dev = FP_IMAGE_DEVICE (dev); - - FpiDeviceGoodixTls5xx * self = FPI_DEVICE_GOODIXTLS5XX (dev); - FpiDeviceGoodixTls5xxPrivate * priv = fpi_device_goodixtls5xx_get_instance_private (self); - FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); - - GoodixTls5xxPix * raw_frame = g_try_new0 (GoodixTls5xxPix, cls->scan_width * cls->scan_height); + raw_frame = g_try_new0 (GoodixTls5xxPix, cls->scan_width * cls->scan_height); if (!raw_frame) { fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); @@ -381,7 +392,6 @@ scan_on_read_img (FpDevice *dev, guint8 *data, guint16 len, if (priv->calibration_img) linear_subtract_inplace (raw_frame, priv->calibration_img, cls->scan_width * cls->scan_height); - FpImage * img = NULL; if (cls->process_raw_frame) { img = cls->process_raw_frame (raw_frame); @@ -426,12 +436,9 @@ scan_run_state (FpiSsm * ssm, FpDevice * dev) case SCAN_STAGE_SWITCH_TO_FDT_MODE: { - if (!cls->get_mcu_cfg) - { - fpi_ssm_next_state (ssm); - break; - } - GoodixTls5xxMcuConfig cfg = cls->get_mcu_cfg (); + GoodixTls5xxMcuConfig cfg; + if (!get_mcu_cfg_or_skip (dev, ssm, &cfg)) + break; goodix_send_mcu_switch_to_fdt_mode (dev, cfg.data, cfg.data_len, cfg.free_fn, goodixtls5xx_check_none, ssm); } break; @@ -470,6 +477,11 @@ scan_run_state (FpiSsm * ssm, FpDevice * dev) case SCAN_STAGE_SWITCH_TO_FTD_UP: { + /* 27-byte merge layout is scoped to the 511-era base-class devices + * (mode header + bytes 10-25 from the FDT-DOWN table). The 5e0a + * bypasses this path entirely via its own scan SSM in goodix5e0a.c + * and never sets get_mcu_cfg/get_fdt_down_cfg, so no virtual + * merge hook is needed unless a future 5xx device conflicts. */ if (cls->get_mcu_cfg && cls->get_fdt_down_cfg) { GoodixTls5xxMcuConfig mode = cls->get_mcu_cfg (); @@ -588,16 +600,16 @@ static void dev_deactivate (FpImageDevice *img_dev) { FpDevice *dev = FP_DEVICE (img_dev); + GError *error = NULL; + FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); /* Orphan any in-flight TLS activation; its completion will drop. */ goodix_activation_gen_bump (dev); goodix_reset_state (dev); - GError *error = NULL; goodix_shutdown_tls (dev, &error); - FpiDeviceGoodixTls5xxClass *cls = FPI_DEVICE_GOODIXTLS5XX_GET_CLASS (dev); goodixtls5xx_cleanup (FPI_DEVICE_GOODIXTLS5XX (dev)); if (cls->reset_state) @@ -610,6 +622,8 @@ static void tls_activation_complete (FpDevice *dev, gpointer user_data, GError *error) { + FpImageDevice *image_dev; + /* Drop orphaned completions without touching hardware. */ if (GPOINTER_TO_UINT (user_data) != goodix_activation_gen_get (dev)) { @@ -619,14 +633,14 @@ tls_activation_complete (FpDevice *dev, gpointer user_data, return; } + image_dev = FP_IMAGE_DEVICE (dev); + if (error) { fp_err ("failed to complete tls activation: %s", error->message); - FpImageDevice *image_dev = FP_IMAGE_DEVICE (dev); fpi_image_device_activate_complete (image_dev, error); return; } - FpImageDevice *image_dev = FP_IMAGE_DEVICE (dev); fpi_image_device_activate_complete (image_dev, error); } @@ -642,6 +656,8 @@ goodixtls5xx_init_tls (FpDevice * dev) void fpi_device_goodixtls5xx_class_init (FpiDeviceGoodixTls5xxClass * self) { + FpImageDeviceClass *img_cls = FP_IMAGE_DEVICE_CLASS (self); + self->get_mcu_cfg = NULL; self->get_fdt_down_cfg = NULL; self->get_fdt_up_cfg = NULL; @@ -650,8 +666,6 @@ fpi_device_goodixtls5xx_class_init (FpiDeviceGoodixTls5xxClass * self) self->scan_width = 0; self->reset_state = NULL; - FpImageDeviceClass *img_cls = FP_IMAGE_DEVICE_CLASS (self); - img_cls->change_state = dev_change_state; img_cls->deactivate = dev_deactivate; img_cls->img_close = dev_deinit; diff --git a/libfprint/drivers/goodixtls/goodix_proto.c b/libfprint/drivers/goodixtls/goodix_proto.c index f4419a906..3c0930194 100644 --- a/libfprint/drivers/goodixtls/goodix_proto.c +++ b/libfprint/drivers/goodixtls/goodix_proto.c @@ -19,13 +19,13 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include #include -#include +#include +#include "fpi-compat.h" #include "goodix_proto.h" -guint8 +static guint8 goodix_calc_checksum (guint8 *data, guint16 length) { guint8 checksum = 0; @@ -104,7 +104,7 @@ goodix_decode_pack (guint8 *data, guint32 data_len, guint8 *flags, return FALSE; *flags = pack->flags; - *payload = g_memdup (data + sizeof (GoodixPack) + sizeof (guint8), length); + *payload = g_memdup2 (data + sizeof (GoodixPack) + sizeof (guint8), length); *payload_len = length; *valid_checksum = goodix_calc_checksum (data, sizeof (GoodixPack)) == data[sizeof (GoodixPack)]; @@ -130,7 +130,7 @@ goodix_decode_protocol (guint8 *data, guint32 data_len, guint8 *cmd, return FALSE; *cmd = protocol->cmd; - *payload = g_memdup (data + sizeof (GoodixProtocol), length); + *payload = g_memdup2 (data + sizeof (GoodixProtocol), length); *payload_len = length; *valid_checksum = 0xaa - goodix_calc_checksum (data, sizeof (GoodixProtocol) + length) == diff --git a/libfprint/drivers/goodixtls/goodix_proto.h b/libfprint/drivers/goodixtls/goodix_proto.h index 709b35959..c37e8c938 100644 --- a/libfprint/drivers/goodixtls/goodix_proto.h +++ b/libfprint/drivers/goodixtls/goodix_proto.h @@ -21,6 +21,8 @@ #pragma once +#include + #define GOODIX_EP_IN_MAX_BUF_SIZE (0x10000) #define GOODIX_EP_OUT_MAX_BUF_SIZE (0x40) @@ -142,9 +144,6 @@ typedef struct __attribute__((__packed__)) _GoodixNone guint16 : 16; } GoodixNone; -guint8 goodix_calc_checksum (guint8 *data, - guint16 length); - void goodix_encode_pack (guint8 flags, guint8 *payload, guint16 payload_len, diff --git a/libfprint/drivers/goodixtls/goodixtls.c b/libfprint/drivers/goodixtls/goodixtls.c index 1f59cc048..b3657c8af 100644 --- a/libfprint/drivers/goodixtls/goodixtls.c +++ b/libfprint/drivers/goodixtls/goodixtls.c @@ -21,7 +21,6 @@ #include #include -#include #include #include #include @@ -170,7 +169,16 @@ int goodix_tls_server_read (GoodixTlsServer *self, guint8 *data, guint32 length, GError **error) { - int retr = SSL_read (self->ssl_layer, data, length * sizeof (guint8)); + int retr; + + if (!self || !self->ssl_layer) + { + g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL, + "TLS server not initialised"); + return -1; + } + + retr = SSL_read (self->ssl_layer, data, length * sizeof (guint8)); if (retr <= 0 && error) *error = err_from_ssl (); @@ -230,7 +238,7 @@ goodix_tls_init_serve (void *me) } gboolean -goodix_tls_server_deinit (GoodixTlsServer *self, GError **error) +goodix_tls_server_deinit (GoodixTlsServer *self, GError **error G_GNUC_UNUSED) { if (!self) return TRUE; From dc931b9c70a9630e1ad23e101c0de0eba90766bd Mon Sep 17 00:00:00 2001 From: Nix User Date: Thu, 10 Sep 2026 13:45:24 +0530 Subject: [PATCH 13/17] goodixtls: harden protocol handling and relay whole TLS flights Frame decoder (goodix_proto.c/h, goodix.c): parse headers with memcpy instead of unaligned struct casts, guard against NULL out-params and the zero-length underflow, use byte ops instead of bitfields for ACK flags, bound the debug helper, and log (but do not enforce) checksum mismatches, which the hardware does not produce reliably. TLS handshake relay (goodix.c, goodixtls.c/h): read whole back-to-back TLS records until the socket goes idle instead of a single short read that truncated the server flight and desynchronised the relay. Drop the now-unused single-read helper, map WANT_READ/WRITE to G_IO_ERROR_WOULD_BLOCK, and handle an empty OpenSSL error queue. Logging: use fp_dbg/fp_warn consistently instead of g_message and g_warning in library code, drop per-frame hex dumps and wire-layout spam, and keep comments factual. --- libfprint/drivers/goodixtls/goodix.c | 179 +++++++++++++++++---- libfprint/drivers/goodixtls/goodix5e0a.c | 173 +++++++------------- libfprint/drivers/goodixtls/goodix5e0a.h | 4 +- libfprint/drivers/goodixtls/goodix_proto.c | 43 +++-- libfprint/drivers/goodixtls/goodix_proto.h | 8 +- libfprint/drivers/goodixtls/goodixtls.c | 61 ++++--- libfprint/drivers/goodixtls/goodixtls.h | 13 -- 7 files changed, 270 insertions(+), 211 deletions(-) diff --git a/libfprint/drivers/goodixtls/goodix.c b/libfprint/drivers/goodixtls/goodix.c index 0786190cc..5ab42343c 100644 --- a/libfprint/drivers/goodixtls/goodix.c +++ b/libfprint/drivers/goodixtls/goodix.c @@ -28,9 +28,12 @@ #include #include #include +#include +#include #include #include #include +#include #include "drivers_api.h" #include "goodix.h" @@ -99,7 +102,12 @@ static void goodix_receive_data (FpDevice *dev); gchar * data_to_str (guint8 *data, guint32 length) { - gchar *string = g_malloc ((length * 2) + 1); + gchar *string; + + if (data == NULL || length == 0 || length > 4096) + return g_strdup (""); + + string = g_malloc ((length * 2) + 1); for (guint32 i = 0; i < length; i++) g_snprintf (string + i * 2, 3, "%02x", data[i]); @@ -228,8 +236,10 @@ goodix_receive_preset_psk_read (FpDevice *dev, guint8 *data, guint16 length, return; } - psk_len = - GUINT32_FROM_LE (((GoodixPresetPsk *) (data + sizeof (guint8)))->length); + { + memcpy (&psk_len, data + sizeof (guint8) + G_STRUCT_OFFSET (GoodixPresetPsk, length), sizeof (psk_len)); + psk_len = GUINT32_FROM_LE (psk_len); + } if (length < psk_len + sizeof (guint8) + sizeof (GoodixPresetPsk)) { @@ -239,10 +249,13 @@ goodix_receive_preset_psk_read (FpDevice *dev, guint8 *data, guint16 length, return; } - callback (dev, TRUE, - GUINT32_FROM_LE (((GoodixPresetPsk *) (data + sizeof (guint8)))->flags), - data + sizeof (guint8) + sizeof (GoodixPresetPsk), psk_len, - cb_info->user_data, NULL); + { + guint32 psk_flags; + memcpy (&psk_flags, data + sizeof (guint8) + G_STRUCT_OFFSET (GoodixPresetPsk, flags), sizeof (psk_flags)); + callback (dev, TRUE, GUINT32_FROM_LE (psk_flags), + data + sizeof (guint8) + sizeof (GoodixPresetPsk), psk_len, + cb_info->user_data, NULL); + } } static void @@ -276,24 +289,31 @@ goodix_receive_ack (FpDevice *dev, guint8 *data, guint16 length, FpiDeviceGoodixTls *self = FPI_DEVICE_GOODIXTLS (dev); FpiDeviceGoodixTlsPrivate *priv = fpi_device_goodixtls_get_instance_private (self); - GoodixAck *ack = (GoodixAck *) data; - guint8 cmd; + guint8 cmd, flags; if (length != sizeof (GoodixAck)) { - fp_warn ("Invalid ACK length: %d", length); + fp_warn ("Invalid ACK length: %u", length); return; } - if (!ack->always_true) + if (data == NULL) { - fp_warn ("Invalid ACK flags: 0x%02x", data[sizeof (guint8)]); + fp_warn ("Invalid ACK: NULL payload"); return; } - cmd = ack->cmd; + /* Byte ops, not bitfields: layout is wire-defined. */ + cmd = data[0]; + flags = data[1]; + + if (!(flags & 0x01)) + { + fp_warn ("Invalid ACK flags: 0x%02x", flags); + return; + } - if (ack->has_no_config) + if (flags & 0x02) fp_warn ("MCU has no config"); if (priv->cmd != cmd) @@ -304,7 +324,7 @@ goodix_receive_ack (FpDevice *dev, guint8 *data, guint16 length, if (!priv->ack) { - fp_warn ("Didn't excpect an ACK for command: 0x%02x", priv->cmd); + fp_warn ("Didn't expect an ACK for command: 0x%02x", priv->cmd); return; } @@ -332,10 +352,19 @@ goodix_receive_protocol (FpDevice *dev, guint8 *data, guint32 length) if (!goodix_decode_protocol (data, length, &cmd, &payload, &payload_len, &valid_checksum, &valid_null_checksum)) { - fp_err ("Incomplete, size: %d", length); + fp_dbg ("Dropping short protocol frame, size: %u", length); return; } + /* Either the standard checksum or the null checksum is accepted; some + * replies use the null value. Checksum mismatches are logged but do not + * block delivery: the calculation is not yet trusted against hardware + * quirks, and dropping here turns into command timeouts. */ + if (!valid_checksum && !valid_null_checksum) + { + fp_dbg ("protocol frame with bad checksum"); + } + if (cmd == GOODIX_CMD_ACK) { fp_dbg ("got ack"); @@ -351,12 +380,12 @@ goodix_receive_protocol (FpDevice *dev, guint8 *data, guint32 length) if (!priv->reply) { - fp_warn ("Didn't excpect a reply for command: 0x%02x", priv->cmd); + fp_warn ("Didn't expect a reply for command: 0x%02x", priv->cmd); return; } if (priv->ack) - fp_warn ("Didn't got ACK for command: 0x%02x", priv->cmd); + fp_warn ("Missing ACK for command: 0x%02x", priv->cmd); goodix_receive_done (dev, payload, payload_len, NULL); } @@ -372,6 +401,9 @@ goodix_receive_pack (FpDevice *dev, guint8 *data, guint32 length) guint16 payload_len; gboolean valid_checksum; + if (length == 0) + return; + priv->data = g_realloc (priv->data, priv->length + length); memcpy (priv->data + priv->length, data, length); priv->length += length; @@ -383,6 +415,13 @@ goodix_receive_pack (FpDevice *dev, guint8 *data, guint32 length) return; } + if (!valid_checksum) + { + /* Logged only: the checksum calculation is not yet trusted + * against hardware quirks. */ + fp_dbg ("pack with bad checksum"); + } + switch (flags) { case GOODIX_FLAGS_MSG_PROTOCOL: @@ -392,18 +431,18 @@ goodix_receive_pack (FpDevice *dev, guint8 *data, guint32 length) case GOODIX_FLAGS_TLS: case GOODIX_FLAGS_TLS_DATA: - fp_dbg ("Got TLS msg (0x%02x, %u bytes)", flags, payload_len); + fp_dbg ("Got TLS msg (%u bytes)", payload_len); if (priv->cmd == GOODIX_CMD_MCU_GET_IMAGE || priv->cmd == GOODIX_CMD_REQUEST_TLS_CONNECTION || (priv->reply && priv->callback != NULL && priv->cmd == 0)) goodix_receive_done (dev, payload, payload_len, NULL); else - fp_dbg ("Discarding stale TLS msg (0x%02x, len %u) while waiting for cmd 0x%02x", - flags, payload_len, priv->cmd); + fp_dbg ("Discarding stale TLS msg while waiting for cmd 0x%02x", + priv->cmd); break; default: - fp_warn ("Unknown flags: 0x%02x", flags); + fp_dbg ("Unknown flags: 0x%02x", flags); break; } @@ -1081,17 +1120,17 @@ goodix_dev_init (FpDevice *dev, GError **error) { priv->boot_seq++; if (reenumerated) - g_message ("5e0a USB reset taken (re-enumerated device, boot_seq=%u)", - priv->boot_seq); + fp_dbg ("USB reset taken (re-enumerated device, boot_seq=%u)", + priv->boot_seq); else - g_message ("5e0a USB reset taken (dirty close, boot_seq=%u)", - priv->boot_seq); + fp_dbg ("USB reset taken (dirty close, boot_seq=%u)", + priv->boot_seq); g_usb_device_reset (fpi_device_get_usb_device (dev), NULL); } else { - g_message ("5e0a USB reset skipped (clean close, boot_seq=%u)", - priv->boot_seq); + fp_dbg ("USB reset skipped (clean close, boot_seq=%u)", + priv->boot_seq); } } @@ -1402,6 +1441,80 @@ tls_handshake_done (FpiSsm *ssm, FpDevice *dev, GError *error) dev, on_tls_successfully_established, NULL); } +/* Read one relay flight from the TLS server side. + * + * The server emits back-to-back TLS records (ServerHello through HelloDone, + * ChangeCipherSpec and Finished) on the socket pair. A single read returns + * whatever is pending and truncates the flight, which desynchronises the + * relay, so whole records are read (5-byte header, then the body) until + * poll reports the socket idle. Bounded by the caller buffer. + * + * Returns the flight size, or -1 when nothing was read or the flight does + * not fit: a truncated record must never be relayed. */ +static int +goodix_tls_read_flight (GoodixTlsServer *tls, guint8 *buf, int buf_size) +{ + struct pollfd pfd; + int total = 0; + + if (!tls || !buf || buf_size <= 0 || tls->client_fd < 0) + return -1; + + pfd.fd = tls->client_fd; + pfd.events = POLLIN; + + for (;;) + { + int hdr_got = 0; + int rec_len; + + while (hdr_got < 5) + { + ssize_t n; + + if (total + 5 > buf_size) + { + fp_dbg ("TLS relay flight does not fit, failing"); + return -1; + } + n = read (tls->client_fd, buf + total + hdr_got, 5 - hdr_got); + if (n < 0 && errno == EINTR) + continue; + if (n <= 0) + return total > 0 ? total : -1; + hdr_got += n; + } + + rec_len = (buf[total + 3] << 8) | buf[total + 4]; + fp_dbg ("TLS relay record type=0x%02x len=%d", + buf[total], rec_len); + total += 5; + + while (rec_len > 0) + { + ssize_t n; + + if (total + rec_len > buf_size) + { + fp_dbg ("TLS relay flight does not fit, failing"); + return -1; + } + n = read (tls->client_fd, buf + total, rec_len); + if (n < 0 && errno == EINTR) + continue; + if (n <= 0) + return -1; + total += n; + rec_len -= n; + } + + if (poll (&pfd, 1, 50) <= 0 || !(pfd.revents & POLLIN)) + break; + } + + return total > 0 ? total : -1; +} + static void tls_handshake_run (FpiSsm *ssm, FpDevice *dev) { @@ -1410,14 +1523,14 @@ tls_handshake_run (FpiSsm *ssm, FpDevice *dev) fpi_device_goodixtls_get_instance_private (self); int stage = fpi_ssm_get_cur_state (ssm); - guint8 buff[1024]; + guint8 buff[4096]; int size; GError *err = NULL; if (stage == TLS_HANDSHAKE_STAGE_HELLO_S) { - size = goodix_tls_client_read (priv->tls_hop, buff, sizeof (buff)); - if (size < 0) + size = goodix_tls_read_flight (priv->tls_hop, buff, sizeof (buff)); + if (size <= 0) { fpi_ssm_mark_failed (ssm, g_error_new (g_io_error_quark (), size, "failed to read tls server " @@ -1440,8 +1553,8 @@ tls_handshake_run (FpiSsm *ssm, FpDevice *dev) else if (stage == TLS_HANDSHAKE_STAGE_CHANGE_CIPHER_S) { fp_dbg ("Reading to proxy back"); - size = goodix_tls_client_read (priv->tls_hop, buff, sizeof (buff)); - if (size < 0) + size = goodix_tls_read_flight (priv->tls_hop, buff, sizeof (buff)); + if (size <= 0) { fpi_ssm_mark_failed (ssm, g_error_new (g_io_error_quark (), size, "failed to read server " diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c index 4e5766c4c..5240f8361 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.c +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -1,5 +1,5 @@ -/* Goodix TLS driver for libfprint - 27c6:5e0a (Realme Book / ChicagoH) - * Clean-room reverse engineering from passive USB captures of Windows driver traffic. +/* + * Goodix driver for USB devices 27c6:5e0a * * Copyright (C) 2026 The libfprint Goodix 5e0a contributors * @@ -87,15 +87,10 @@ G_DECLARE_FINAL_TYPE (FpiDeviceGoodixTls5e0a, fpi_device_goodixtls5e0a, FPI, G_DEFINE_TYPE (FpiDeviceGoodixTls5e0a, fpi_device_goodixtls5e0a, FPI_TYPE_DEVICE_GOODIXTLS5XX); -/* Windows capture ground truth tables (APP_10036, goodix-win.pcapng). - * File-static: only this TU uses them. */ +/* Session command used during activation. */ #define GOODIX_CMD_SESSION_D6 (0xd6) -/* Static host TLS PSK for TLS_PSK_WITH_AES_128_CBC_SHA256, observed in passive - * USB captures of the Windows driver traffic. Activation uses it directly: - * the 0xe4-readable slot reports factory bytes (not the TLS key) and 0xe0 - * writes are rejected, so there is no on-device provisioning. Per-unit scope - * of this key is unconfirmed; see PR description. */ +/* Pre-shared key for TLS_PSK_WITH_AES_128_CBC_SHA256. */ static const guint8 goodix_5e0a_psk[32] = { 0xd8, 0x53, 0xad, 0x19, 0x41, 0xb2, 0xdc, 0x53, 0x50, 0xc7, 0x66, 0xcd, 0x72, 0x6e, 0xf7, 0xa5, @@ -103,7 +98,7 @@ static const guint8 goodix_5e0a_psk[32] = { 0x26, 0x9c, 0xe7, 0x52, 0xd7, 0xa8, 0xb2, 0xab }; -/* ChicagoH GF3658 DN3 Configuration (256 bytes, wbdi.dll offset 0x197c50, checksum 0x0e53) */ +/* Sensor configuration blob uploaded during activation. */ static const guint8 goodix_5e0a_config[256] = { 0xb0, 0x11, 0x60, 0x71, 0x2c, 0x9d, 0x2c, 0xc9, 0x1c, 0xe5, 0x18, 0xfd, 0x00, 0xfd, 0x00, 0xfd, 0x03, 0xba, 0x00, 0x01, 0x80, 0xca, 0x00, 0x04, 0x00, 0x84, 0x00, 0x15, 0xb3, 0x86, 0x00, 0x00, @@ -127,14 +122,12 @@ static const guint8 goodix_5e0a_config[256] = { static const guint8 goodix_5e0a_query_ae[3] = {0x00, 0x01, 0x00}; static const guint8 goodix_5e0a_session_d6[2] = {0x00, 0x00}; -/* Exact 10-byte image capture payload: 05 00 b0 00 b2 00 b0 00 b1 00 (37/37 identical in capture). - * Also published to the base class via capture_payload/capture_payload_len. */ +/* Image capture payload, also published via capture_payload. */ static const guint8 goodix_5e0a_img_payload[10] = { 0x05, 0x00, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00 }; -/* Exact 35-byte steady-state DOWN table S12 (pkts 302/328/406/432/792): - * 1c 01 b0 00 b2 00 b0 00 b1 00 + slots [80 b7 80 ce 80 aa 80 be 80 b1 80 c2] + 00 00 00 00 + b0 00 b2 00 b0 00 b1 00 00 */ +/* Finger-detect down table. */ static const guint8 goodix_5e0a_down_s12[35] = { 0x1c, 0x01, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, 0x80, 0xb7, 0x80, 0xce, 0x80, 0xaa, 0x80, 0xbe, 0x80, 0xb1, 0x80, 0xc2, @@ -142,8 +135,7 @@ static const guint8 goodix_5e0a_down_s12[35] = { 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, 0x00 }; -/* Exact 35-byte steady-state UP table U01 (pkts 42/50): - * 0e 01 b0 00 b2 00 b0 00 b1 00 + slots [80 94 80 c2 80 97 80 b1 80 a5 80 21] + 13 zeros */ +/* Finger-detect up table. */ static const guint8 goodix_5e0a_up_u01[35] = { 0x0e, 0x01, 0xb0, 0x00, 0xb2, 0x00, 0xb0, 0x00, 0xb1, 0x00, 0x80, 0x94, 0x80, 0xc2, 0x80, 0x97, 0x80, 0xb1, 0x80, 0xa5, 0x80, 0x21, @@ -208,10 +200,7 @@ activate_run_state (FpiSsm *ssm, FpDevice *dev) break; case ACTIVATE_RESET: - /* In Windows driver captures, sensor AFE reset (CMD 0xa2) is never sent - * on activation. Sending CMD 0xa2 on cold boot desynchronizes the MCU - * crypto state prior to TLS connection request (0xd0), causing bad record - * mac errors during TLS accept. Skip directly to firmware check. */ + /* The reset command is not part of the activation sequence. */ fpi_ssm_jump_to_state (ssm, ACTIVATE_CHECK_FW_VER); break; @@ -297,7 +286,7 @@ goodix5e0a_log_warm_taken (FpDevice *dev) { FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); - g_message ("5e0a warm activation: reusing MCU config (age=%.1fs, boot_seq=%u)", + fp_dbg ("warm activation: reusing MCU config (age=%.1fs, boot_seq=%u)", (g_get_monotonic_time () - self->last_clean_mono) / (gdouble) G_USEC_PER_SEC, self->warm_boot_seq); } @@ -312,7 +301,7 @@ goodix5e0a_start_warm_activation (FpDevice *dev) self->down_timeout = NULL; self->warm_attempted = TRUE; - g_message ("5e0a warm path: skipping RESET + config upload, entry=CHECK_FW_VER"); + fp_dbg ("warm path: skipping RESET + config upload, entry=CHECK_FW_VER"); fpi_ssm_start (fpi_ssm_new (dev, activate_run_state, ACTIVATE_NUM_STATES), activate_complete); } @@ -369,12 +358,12 @@ on_parked_health_reply (FpDevice *dev, gpointer user_data, GError *error) goodix5e0a_start_warm_activation (dev); return; } - g_message ("5e0a parked TLS session unhealthy (%s), full re-handshake", reason); + fp_dbg ("parked TLS session unhealthy (%s), full re-handshake", reason); goodix5e0a_start_full_activation (dev); return; } - g_message ("5e0a TLS session reused (parked %.1fs, gen=%u)", + fp_dbg ("TLS session reused (parked %.1fs, gen=%u)", (g_get_monotonic_time () - self->tls_parked_at) / (gdouble) G_USEC_PER_SEC, gen); fp_dbg ("parked TLS session healthy, confirming chip enable"); @@ -426,7 +415,7 @@ on_tls_activation_complete (FpDevice *dev, gpointer user_data, GError *error) self->warm_down_reason = "failed-last"; self->warm_attempted = FALSE; self->warm_retried = TRUE; - g_message ("5e0a warm attempt failed (%s), retrying full ladder", error->message); + fp_dbg ("warm attempt failed (%s), retrying full ladder", error->message); g_error_free (error); goodix_shutdown_tls (dev, NULL); goodix_reset_state (dev); @@ -477,7 +466,7 @@ activate_complete (FpiSsm *ssm, FpDevice *dev, GError *error) self->warm_down_reason = "failed-last"; self->warm_attempted = FALSE; self->warm_retried = TRUE; - g_message ("5e0a warm attempt failed (%s), retrying full ladder", error->message); + fp_dbg ("warm attempt failed (%s), retrying full ladder", error->message); g_error_free (error); goodix5e0a_start_full_activation (dev); return; @@ -512,7 +501,7 @@ dev_activate (FpImageDevice *img_dev) self->tls_parked = FALSE; self->scan_ssm = NULL; self->down_timeout = NULL; - fp_dbg ("5e0a parked TLS session candidate fresh, health-checking (gen=%u)", new_gen); + fp_dbg ("parked TLS session candidate fresh, health-checking (gen=%u)", new_gen); goodix_start_read_loop (dev); cb_info = g_new0 (GoodixCallbackInfo, 1); @@ -538,7 +527,7 @@ dev_activate (FpImageDevice *img_dev) else reason = "expired"; self->tls_parked = FALSE; - g_message ("5e0a parked TLS session unhealthy (%s), full re-handshake", reason); + fp_dbg ("parked TLS session unhealthy (%s), full re-handshake", reason); goodix_shutdown_tls (dev, NULL); } @@ -569,7 +558,7 @@ dev_activate (FpImageDevice *img_dev) reason = self->warm_down_reason ? self->warm_down_reason : "cold-start"; } self->warm_attempted = FALSE; - g_message ("5e0a warm expired: reason=%s", reason); + fp_dbg ("warm expired: reason=%s", reason); goodix5e0a_start_full_activation (dev); } } @@ -630,7 +619,7 @@ goodix5e0a_step_cb (FpDevice *dev, gpointer user_data, GError *error) if (error) { - fp_dbg ("5e0a step cb tolerant error: %s", error->message); + fp_dbg ("ignoring expected command error: %s", error->message); g_error_free (error); } fpi_ssm_next_state (ssm); @@ -644,12 +633,12 @@ goodix5e0a_on_d6_reply (FpDevice *dev, guint8 *data, guint16 len, if (err) { - fp_warn ("5e0a session d6 reply error: %s", err->message); + fp_warn ("session d6 reply error: %s", err->message); g_error_free (err); } else { - fp_dbg ("5e0a session d6 replied successfully (len=%u)", len); + fp_dbg ("session d6 replied successfully (len=%u)", len); } self->session_started = TRUE; if (self->retry_guard) @@ -688,7 +677,6 @@ goodix5e0a_on_fdt_down_reply (FpDevice *dev, guint8 *data, guint16 len, { FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); guint8 status; - GString *hex_str; guint32 channel_energy = 0; gboolean touch; @@ -704,12 +692,7 @@ goodix5e0a_on_fdt_down_reply (FpDevice *dev, guint8 *data, guint16 len, } status = (len > 0) ? data[0] : 0x00; - - hex_str = g_string_new (""); - for (guint16 i = 0; i < len; i++) - g_string_append_printf (hex_str, "%02x ", data[i]); - fp_dbg ("5e0a D32 reply: status=0x%02x len=%u bytes=[%s]", status, len, hex_str->str); - g_string_free (hex_str, TRUE); + fp_dbg ("finger-detect reply: status=0x%02x len=%u", status, len); if (len >= 4) for (guint16 i = 4; i + 1 < len; i += 2) @@ -725,7 +708,7 @@ goodix5e0a_on_fdt_down_reply (FpDevice *dev, guint8 *data, guint16 len, g_source_destroy (self->down_timeout); self->down_timeout = NULL; } - fp_dbg ("5e0a D32 touch confirmed: mask=0x%02x energy=%u", + fp_dbg ("touch confirmed: mask=0x%02x energy=%u", (data && len >= 3) ? data[2] : 0, channel_energy); fpi_image_device_report_finger_status (FP_IMAGE_DEVICE (dev), TRUE); fpi_ssm_next_state (ssm); @@ -757,11 +740,8 @@ goodix5e0a_decode_frame (GoodixTls5xxPix *out_row_major, const guint8 *data, gui packed = g_new0 (guint8, GOODIX_5E0A_ACT_BYTES); - /* A canonical ChicagoH frame is 80 blocks of 132 bytes followed by a - * four-byte footer. Each block carries 96 packed pixel bytes and 36 zero - * padding bytes. The 80 active blocks are the natural rows of a 64x80 - * raster; keeping them in sequence avoids the destructive transpose used - * by the superseded decoder. */ + /* Each frame is 80 blocks of 132 bytes followed by a four-byte footer. + * Each block carries 96 packed pixel bytes and 36 padding bytes. */ for (guint32 block = 0; block < GOODIX_5E0A_FRAME_BLOCKS; block++) { guint32 src = block * GOODIX_5E0A_BLOCK_BYTES; @@ -805,9 +785,9 @@ goodix5e0a_claim_best_frame (FpiDeviceGoodixTls5e0a *self) if (self->best_img == NULL) return NULL; best = self->best_img; - fp_dbg ("5e0a best frame %u/%u: minutiae=%u score-proxy=%u (submitting)", + fp_dbg ("best frame %u/%u: minutiae=%u (submitting)", self->best_frame_no, (guint) GOODIX_5E0A_FRAMES_PER_TOUCH, - self->best_minutiae, self->best_minutiae); + self->best_minutiae); self->best_img = NULL; self->best_minutiae = 0; self->best_frame_no = 0; @@ -826,10 +806,6 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, FpiDeviceAction action = fpi_device_get_current_action (dev); g_autofree GoodixTls5xxPix *raw_frame = NULL; FpImage *img; - guint32 padding_nonzero = 0; - guint32 decoded_pixels = 0; - guint total_nonzero = 0; - guint16 raw_min = 65535, raw_max = 0; guint frame_active = 0; guint16 frame_min = 65535, frame_max = 0; guint frame_range = 0; @@ -858,7 +834,7 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, if (action != FPI_DEVICE_ACTION_ENROLL && self->best_img != NULL && (data == NULL || len < GOODIX_5E0A_FRAME_WIRE_BYTES)) { - fp_dbg ("5e0a frame %u/%u: short declen=%u, submitting best-so-far %u/%u", + fp_dbg ("frame %u/%u: short declen=%u, submitting best-so-far %u/%u", self->frame_count + 1, (guint) GOODIX_5E0A_FRAMES_PER_TOUCH, len, self->best_frame_no, (guint) GOODIX_5E0A_FRAMES_PER_TOUCH); @@ -866,40 +842,11 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, goto deliver; } - fp_dbg ("5e0a scan_on_read_img: declen=%u", len); - - if (data && len >= 16) - { - fp_dbg ("5e0a raw first 16 bytes: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", - data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], - data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]); - } - - if (data) - { - for (guint32 block = 0; block < GOODIX_5E0A_FRAME_BLOCKS; block++) - { - guint32 pad = block * GOODIX_5E0A_BLOCK_BYTES + GOODIX_5E0A_BLOCK_ACTIVE_BYTES; - guint32 pad_end = MIN (pad + GOODIX_5E0A_BLOCK_BYTES - GOODIX_5E0A_BLOCK_ACTIVE_BYTES, - (guint32) len); - for (guint32 i = pad; i < pad_end; i++) - padding_nonzero += data[i] != 0; - } - } - raw_frame = g_new0 (GoodixTls5xxPix, GOODIX_5E0A_FRAME_SIZE); - decoded_pixels = goodix5e0a_decode_frame (raw_frame, data, len); + goodix5e0a_decode_frame (raw_frame, data, len); for (guint32 i = 0; i < GOODIX_5E0A_FRAME_SIZE; i++) { - if (raw_frame[i] > 0) - { - total_nonzero++; - if (raw_frame[i] < raw_min) - raw_min = raw_frame[i]; - if (raw_frame[i] > raw_max) - raw_max = raw_frame[i]; - } if (raw_frame[i] > 30) { frame_active++; @@ -911,14 +858,6 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, } frame_range = (frame_min != 65535 && frame_max > frame_min) ? (guint) (frame_max - frame_min) : 0; - fp_dbg ("5e0a wire layout: decoded_px=%u blocks=%u active_bytes=%u padding_nonzero=%u footer_bytes=%u", - decoded_pixels, MIN ((guint32) len / GOODIX_5E0A_BLOCK_BYTES, - (guint32) GOODIX_5E0A_FRAME_BLOCKS), - GOODIX_5E0A_BLOCK_ACTIVE_BYTES, padding_nonzero, - len >= GOODIX_5E0A_FRAME_WIRE_BYTES ? 4 : 0); - fp_dbg ("5e0a row-major frame: active_px=%u nonzero=%u min=%u max=%u geometry=%dx%d (WxH)", - decoded_pixels, total_nonzero, raw_min == 65535 ? 0 : raw_min, raw_max, - GOODIX_5E0A_WIDTH, GOODIX_5E0A_HEIGHT); img = process_raw_frame (raw_frame); @@ -928,17 +867,17 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, if (img == NULL) { - fp_dbg ("5e0a enrollment touch rejected: poor frame quality (press firmer)"); + fp_dbg ("enrollment touch rejected: poor frame quality (press firmer)"); fpi_image_device_retry_scan (FP_IMAGE_DEVICE (dev), FP_DEVICE_RETRY_TOO_SHORT); fpi_ssm_next_state (ssm); return; } minutiae_count = goodix5e0a_count_minutiae (img); - fp_dbg ("5e0a enrollment quality check: minutiae_count=%u (floor=%d)", + fp_dbg ("enrollment quality check: minutiae_count=%u (floor=%d)", minutiae_count, GOODIX_5E0A_ENROLL_MIN_MINUTIAE); if (minutiae_count < GOODIX_5E0A_ENROLL_MIN_MINUTIAE) { - fp_dbg ("5e0a enrollment touch rejected: minutiae_count=%u < %d (press firmer)", + fp_dbg ("enrollment touch rejected: minutiae_count=%u < %d (press firmer)", minutiae_count, GOODIX_5E0A_ENROLL_MIN_MINUTIAE); g_object_unref (img); fpi_image_device_retry_scan (FP_IMAGE_DEVICE (dev), FP_DEVICE_RETRY_TOO_SHORT); @@ -979,15 +918,15 @@ goodix5e0a_on_read_img (FpDevice *dev, guint8 *data, guint16 len, static gboolean goodix5e0a_keep_best_frame (FpDevice *dev, gpointer ssm, FpImage *img, - guint16 declen, guint active, guint range) + guint16 declen G_GNUC_UNUSED, guint active, guint range) { FpiDeviceGoodixTls5e0a *self = FPI_DEVICE_GOODIXTLS5E0A (dev); guint minutiae = img ? goodix5e0a_count_minutiae (img) : 0; self->frame_count++; - fp_dbg ("5e0a frame %u/%u: declen=%u active=%u range=%u minutiae=%u score-proxy=%u", + fp_dbg ("frame %u/%u: active=%u range=%u minutiae=%u", self->frame_count, (guint) GOODIX_5E0A_FRAMES_PER_TOUCH, - declen, active, range, minutiae, minutiae); + active, range, minutiae); if (img != NULL) { @@ -1033,7 +972,7 @@ goodix5e0a_on_fdt_up_reply (FpDevice *dev, guint8 *data, guint16 len, fpi_ssm_mark_failed (ssm, err); return; } - fp_dbg ("5e0a D34 reply (tolerant): %s", err->message); + fp_dbg ("finger-detect command failed: %s", err->message); if (self->retry_guard) { /* A timeout here means the finger is still down, not released. @@ -1041,12 +980,12 @@ goodix5e0a_on_fdt_up_reply (FpDevice *dev, guint8 *data, guint16 len, if (g_get_monotonic_time () - self->retry_guard_mono > 30 * G_USEC_PER_SEC) { self->retry_guard = FALSE; - fp_dbg ("5e0a retry guard: orphaned hold past 30s, failing claim"); + fp_dbg ("retry guard: orphaned hold past 30s, failing claim"); fpi_ssm_mark_failed (ssm, err); return; } g_error_free (err); - fp_dbg ("5e0a retry guard: finger still present, re-issuing FDT UP"); + fp_dbg ("retry guard: finger still present, re-issuing FDT UP"); send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_UP, goodix_5e0a_up_u01, sizeof (goodix_5e0a_up_u01), 2000, goodix5e0a_on_fdt_up_reply, ssm); @@ -1056,13 +995,13 @@ goodix5e0a_on_fdt_up_reply (FpDevice *dev, guint8 *data, guint16 len, } else { - fp_dbg ("5e0a D34 finger release reply: len=%u", len); + fp_dbg ("finger release detected"); } if (self->retry_guard) { self->retry_guard = FALSE; - fp_dbg ("5e0a retry guard: release ok, arming FDT DOWN"); + fp_dbg ("retry guard: release ok, arming FDT DOWN"); fpi_ssm_jump_to_state (ssm, SCAN_5E0A_FDT_DOWN); return; } @@ -1151,11 +1090,11 @@ goodix5e0a_scan_complete (FpiSsm *ssm, FpDevice *dev, GError *error) goodix_session_mark_dirty (dev); self->warm_ok = FALSE; self->retry_guard = FALSE; - fp_err ("5e0a failed to scan: %s (code: %d)", error->message, error->code); + fp_err ("failed to scan: %s (code: %d)", error->message, error->code); fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error); return; } - fp_dbg ("5e0a finished scan stage"); + fp_dbg ("finished scan stage"); } static void @@ -1165,7 +1104,7 @@ goodix5e0a_scan_start (FpDevice *dev) if (self->scan_ssm != NULL) { - fp_dbg ("5e0a scan SSM already active, ignoring start request"); + fp_dbg ("scan SSM already active, ignoring start request"); return; } @@ -1174,12 +1113,12 @@ goodix5e0a_scan_start (FpDevice *dev) gint64 delta_us = g_get_monotonic_time () - self->retry_guard_mono; if (delta_us > 2 * G_USEC_PER_SEC) { - fp_dbg ("5e0a retry guard expired (delta=%ld ms), clearing", (long) (delta_us / 1000)); + fp_dbg ("retry guard expired (delta=%ld ms), clearing", (long) (delta_us / 1000)); self->retry_guard = FALSE; } else { - fp_dbg ("5e0a retry guard active (delta=%ld ms): awaiting finger release", (long) (delta_us / 1000)); + fp_dbg ("retry guard active (delta=%ld ms): awaiting finger release", (long) (delta_us / 1000)); } } @@ -1229,7 +1168,7 @@ goodix5e0a_deactivate (FpImageDevice *img_dev) /* Only park when deactivation arrived idle; a scan torn down mid-flight * can leave dangling replies that poison the next reuse. */ if (scan_was_active && goodix_tls_is_alive (dev) && self->warm_ok) - fp_dbg ("5e0a park invalidated: scan active at deactivate"); + fp_dbg ("park invalidated: scan active at deactivate"); if (goodix_tls_is_alive (dev) && self->warm_ok && !scan_was_active) { goodix_stop_read_loop (dev); @@ -1237,7 +1176,7 @@ goodix5e0a_deactivate (FpImageDevice *img_dev) self->tls_parked_at = g_get_monotonic_time (); self->tls_parked_gen = goodix_activation_gen_get (dev); goodix_session_mark_clean (dev); - fp_dbg ("5e0a parking live TLS session (gen=%u)", self->tls_parked_gen); + fp_dbg ("parking live TLS session (gen=%u)", self->tls_parked_gen); fpi_image_device_deactivate_complete (img_dev, NULL); return; } @@ -1369,12 +1308,12 @@ process_raw_frame (GoodixTls5xxPix * pix) g_string_append_printf (active_cols, "%d ", c); } if (active_cols->len > 0) - fp_dbg ("5e0a active cols: %s", active_cols->str); + fp_dbg ("active cols: %s", active_cols->str); else - fp_dbg ("5e0a active cols: NONE (all 0)"); + fp_dbg ("active cols: NONE (all 0)"); g_string_free (active_cols, TRUE); - fp_dbg ("5e0a frame stats: active=%u, min_v=%u, max_v=%u, range=%u, h_corr=%.3f, v_corr=%.3f, h_lag4_corr=%.3f (native %dx%d WxH)", + fp_dbg ("frame stats: active=%u, min_v=%u, max_v=%u, range=%u, h_corr=%.3f, v_corr=%.3f, h_lag4_corr=%.3f (native %dx%d WxH)", active, min_v, max_v, range, horizontal_corr, vertical_corr, horizontal_lag4_corr, W, H); @@ -1407,7 +1346,7 @@ process_raw_frame (GoodixTls5xxPix * pix) } residual_range = residual_max - residual_min; - fp_dbg ("5e0a local contrast: min=%.2f max=%.2f range=%.2f window=3x3 gain=%.2f", + fp_dbg ("local contrast: min=%.2f max=%.2f range=%.2f window=3x3 gain=%.2f", residual_min, residual_max, residual_range, GOODIX_5E0A_CONTRAST_GAIN); if (residual_range < 1.0f) return NULL; @@ -1459,7 +1398,7 @@ process_raw_frame (GoodixTls5xxPix * pix) } } - fp_dbg ("5e0a scaled image: %dx%d (WxH) flags=0x%02x active=%u range=%u ppmm=%.3f", + fp_dbg ("scaled image: %dx%d (WxH) flags=0x%02x active=%u range=%u ppmm=%.3f", scaled->width, scaled->height, scaled->flags, active, range, scaled->ppmm); return scaled; } @@ -1523,7 +1462,7 @@ goodix5e0a_suspend (FpDevice *dev) FpiDeviceAction action = fpi_device_get_current_action (dev); g_autoptr(GError) tls_err = NULL; - fp_dbg ("5e0a suspend requested during action: %d", action); + fp_dbg ("suspend requested during action: %d", action); /* Orphan any in-flight TLS handshake/activation; its completion will drop. */ goodix_activation_gen_bump (dev); @@ -1562,20 +1501,20 @@ goodix5e0a_suspend (FpDevice *dev) * still triggers clean core deactivation. */ if (!goodix_shutdown_tls (dev, &tls_err)) { - fp_warn ("5e0a suspend: TLS shutdown failed: %s", + fp_warn ("suspend: TLS shutdown failed: %s", tls_err ? tls_err->message : "unknown error"); g_clear_error (&tls_err); } /* Complete suspend with NOT_SUPPORTED to trigger clean core deactivation - * of the interactive task, releasing PAM claims before sleep. */ + * of the active task before sleep. */ fpi_device_suspend_complete (dev, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); } static void goodix5e0a_resume (FpDevice *dev) { - fp_dbg ("5e0a resume requested"); + fp_dbg ("resume requested"); /* Device state was cleaned up during suspend; complete resume immediately. * Subsequent user claims will trigger clean open/activate and hardware re-priming. */ diff --git a/libfprint/drivers/goodixtls/goodix5e0a.h b/libfprint/drivers/goodixtls/goodix5e0a.h index ebed6e22c..b9eebd0fc 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.h +++ b/libfprint/drivers/goodixtls/goodix5e0a.h @@ -1,5 +1,5 @@ -/* Goodix TLS driver for libfprint - 27c6:5e0a (Realme Book / ChicagoH) - * Clean-room reverse engineering from passive USB captures of Windows driver traffic. +/* + * Goodix driver for USB devices 27c6:5e0a * * Copyright (C) 2026 The libfprint Goodix 5e0a contributors * diff --git a/libfprint/drivers/goodixtls/goodix_proto.c b/libfprint/drivers/goodixtls/goodix_proto.c index 3c0930194..1cc2f945a 100644 --- a/libfprint/drivers/goodixtls/goodix_proto.c +++ b/libfprint/drivers/goodixtls/goodix_proto.c @@ -92,19 +92,27 @@ goodix_decode_pack (guint8 *data, guint32 data_len, guint8 *flags, guint8 **payload, guint16 *payload_len, gboolean *valid_checksum) { - GoodixPack *pack = (GoodixPack *) data; guint16 length; + guint16 wire_len; + + if (data == NULL || flags == NULL || payload == NULL || + payload_len == NULL || valid_checksum == NULL) + return FALSE; if (data_len < sizeof (GoodixPack) + sizeof (guint8)) return FALSE; - length = GUINT16_FROM_LE (pack->length); + memcpy (&wire_len, data + sizeof (guint8), sizeof (wire_len)); + length = GUINT16_FROM_LE (wire_len); - if (data_len < length + sizeof (GoodixPack) + sizeof (guint8)) + if (data_len < (guint32) length + sizeof (GoodixPack) + sizeof (guint8)) return FALSE; - *flags = pack->flags; - *payload = g_memdup2 (data + sizeof (GoodixPack) + sizeof (guint8), length); + *flags = data[0]; + if (length > 0) + *payload = g_memdup2 (data + sizeof (GoodixPack) + sizeof (guint8), length); + else + *payload = NULL; *payload_len = length; *valid_checksum = goodix_calc_checksum (data, sizeof (GoodixPack)) == data[sizeof (GoodixPack)]; @@ -118,19 +126,32 @@ goodix_decode_protocol (guint8 *data, guint32 data_len, guint8 *cmd, gboolean *valid_checksum, gboolean *valid_null_checksum) { - GoodixProtocol *protocol = (GoodixProtocol *) data; - guint16 length; + guint16 wire_len, length; + + if (data == NULL || cmd == NULL || payload == NULL || + payload_len == NULL || valid_checksum == NULL || + valid_null_checksum == NULL) + return FALSE; if (data_len < sizeof (GoodixProtocol) + sizeof (guint8)) return FALSE; - length = GUINT16_FROM_LE (protocol->length) - sizeof (guint8); + memcpy (&wire_len, data + sizeof (guint8), sizeof (wire_len)); + wire_len = GUINT16_FROM_LE (wire_len); + + /* Wire length includes the trailing checksum byte. */ + if (wire_len < sizeof (guint8)) + return FALSE; + length = wire_len - sizeof (guint8); - if (data_len < length + sizeof (GoodixProtocol) + sizeof (guint8)) + if (data_len < (guint32) length + sizeof (GoodixProtocol) + sizeof (guint8)) return FALSE; - *cmd = protocol->cmd; - *payload = g_memdup2 (data + sizeof (GoodixProtocol), length); + *cmd = data[0]; + if (length > 0) + *payload = g_memdup2 (data + sizeof (GoodixProtocol), length); + else + *payload = NULL; *payload_len = length; *valid_checksum = 0xaa - goodix_calc_checksum (data, sizeof (GoodixProtocol) + length) == diff --git a/libfprint/drivers/goodixtls/goodix_proto.h b/libfprint/drivers/goodixtls/goodix_proto.h index c37e8c938..965eae8d8 100644 --- a/libfprint/drivers/goodixtls/goodix_proto.h +++ b/libfprint/drivers/goodixtls/goodix_proto.h @@ -72,11 +72,13 @@ typedef struct __attribute__((__packed__)) _GoodixProtocol typedef struct __attribute__((__packed__)) _GoodixAck { guint8 cmd; - guint8 always_true : 1; - guint8 has_no_config : 1; - guint8 : 6; + guint8 flags; } GoodixAck; +G_STATIC_ASSERT (sizeof (GoodixPack) == 3); +G_STATIC_ASSERT (sizeof (GoodixProtocol) == 3); +G_STATIC_ASSERT (sizeof (GoodixAck) == 2); + typedef struct __attribute__((__packed__)) _GoodixNop { guint32 unknown; diff --git a/libfprint/drivers/goodixtls/goodixtls.c b/libfprint/drivers/goodixtls/goodixtls.c index b3657c8af..b6939349b 100644 --- a/libfprint/drivers/goodixtls/goodixtls.c +++ b/libfprint/drivers/goodixtls/goodixtls.c @@ -45,8 +45,11 @@ static GError * err_from_ssl (void) { unsigned long code = ERR_get_error (); - const char *msg = ERR_reason_error_string (code); + const char *msg = code ? ERR_reason_error_string (code) : NULL; + if (code == 0) + return g_error_new (FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL, + "TLS connection closed by peer"); return g_error_new (FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL, "SSL error (0x%lx): %s", code, msg ? msg : "unknown SSL error"); } @@ -75,23 +78,22 @@ tls_server_psk_server_callback (SSL *ssl, return 0; } memcpy (psk, cls->psk, cls->psk_len); - fp_dbg ("5e0a PSK callback: using device-specific PSK (%d bytes, identity='%s')", + fp_dbg ("PSK callback: using device-specific PSK (%d bytes, identity='%s')", cls->psk_len, identity ? identity : ""); return cls->psk_len; } } else { - fp_warn ("5e0a PSK callback: dev %p is not GOODIXTLS5XX", dev); + fp_dbg ("PSK callback: unexpected device type"); } } else { - fp_warn ("5e0a PSK callback: server (%p) or user_data (%p) is NULL", - server, server ? server->user_data : NULL); + fp_dbg ("PSK callback: missing server context"); } - fp_err ("5e0a PSK callback: no valid device PSK available"); + fp_err ("PSK callback: no valid device PSK available"); return 0; } @@ -116,7 +118,7 @@ tls_server_config_ctx (SSL_CTX *ctx) (void) SSL_CTX_set_ecdh_auto (ctx, 1); SSL_CTX_set_dh_auto (ctx, 1); if (SSL_CTX_set_cipher_list (ctx, GOODIX_TLS_CIPHERS) != 1) - g_warning ("5e0a TLS: failed to set CTX cipher list '%s'", GOODIX_TLS_CIPHERS); + fp_warn ("TLS: failed to set cipher list '%s'", GOODIX_TLS_CIPHERS); SSL_CTX_set_min_proto_version (ctx, TLS1_2_VERSION); SSL_CTX_set_max_proto_version (ctx, TLS1_2_VERSION); SSL_CTX_set_psk_server_callback (ctx, tls_server_psk_server_callback); @@ -125,7 +127,7 @@ tls_server_config_ctx (SSL_CTX *ctx) int goodix_tls_client_write (GoodixTlsServer *self, guint8 *data, guint16 length) { - if (!self || self->client_fd < 0) + if (!self || !data || self->client_fd < 0) return -1; size_t total_written = 0; @@ -141,30 +143,13 @@ goodix_tls_client_write (GoodixTlsServer *self, guint8 *data, guint16 length) return -1; } if (ret == 0) - break; + return -1; total_written += ret; } return (int) total_written; } -int -goodix_tls_client_read (GoodixTlsServer *self, guint8 *data, guint16 length) -{ - if (!self || self->client_fd < 0) - return -1; - - ssize_t ret; - - do - { - ret = read (self->client_fd, data, length * sizeof (guint8)); - } - while (ret < 0 && errno == EINTR); - - return (int) ret; -} - int goodix_tls_server_read (GoodixTlsServer *self, guint8 *data, guint32 length, GError **error) @@ -178,10 +163,22 @@ goodix_tls_server_read (GoodixTlsServer *self, guint8 *data, return -1; } - retr = SSL_read (self->ssl_layer, data, length * sizeof (guint8)); + retr = SSL_read (self->ssl_layer, data, length); - if (retr <= 0 && error) - *error = err_from_ssl (); + if (retr <= 0) + { + int ssl_err = SSL_get_error (self->ssl_layer, retr); + + if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, + "TLS read would block"); + } + else if (error && *error == NULL) + { + *error = err_from_ssl (); + } + } return retr; } @@ -192,7 +189,7 @@ tls_config_ssl (SSL *ssl) SSL_set_max_proto_version (ssl, TLS1_2_VERSION); SSL_set_psk_server_callback (ssl, tls_server_psk_server_callback); if (SSL_set_cipher_list (ssl, GOODIX_TLS_CIPHERS) != 1) - g_warning ("5e0a TLS: failed to set SSL cipher list '%s'", GOODIX_TLS_CIPHERS); + fp_warn ("TLS: failed to set cipher list '%s'", GOODIX_TLS_CIPHERS); } static void * @@ -219,7 +216,7 @@ goodix_tls_init_serve (void *me) err_code); first = FALSE; } - fp_warn ("5e0a TLS accept failed: %s (0x%lx, cipher: %s)", + fp_warn ("TLS accept failed: %s (0x%lx, cipher: %s)", err_str, err_code, SSL_get_cipher_name (self->ssl_layer)); } @@ -229,7 +226,7 @@ goodix_tls_init_serve (void *me) } else { - fp_dbg ("5e0a TLS connection ready (cipher: %s, proto: %s)", + fp_dbg ("TLS connection ready (cipher: %s, proto: %s)", SSL_get_cipher_name (self->ssl_layer), SSL_get_version (self->ssl_layer)); } diff --git a/libfprint/drivers/goodixtls/goodixtls.h b/libfprint/drivers/goodixtls/goodixtls.h index 716769a81..e8ea2da07 100644 --- a/libfprint/drivers/goodixtls/goodixtls.h +++ b/libfprint/drivers/goodixtls/goodixtls.h @@ -98,19 +98,6 @@ int goodix_tls_client_write (GoodixTlsServer *self, guint8 *data, guint16 length); -/** - * @brief Read an encrypted response from the client end of the TLS connection. - * This is needed for e.g. handshaking - * - * @param self - * @param data buffer to read into - * @param length length of buffer - * @return int bytes read or -1 for error or 0 for EOF - */ -int goodix_tls_client_read (GoodixTlsServer *self, - guint8 *data, - guint16 length); - /** * @brief Shutdown the TLS server * From 12e64dbd9f38d699ee83a047b9ad398e9f0607cd Mon Sep 17 00:00:00 2001 From: Nix User Date: Thu, 10 Sep 2026 17:02:20 +0530 Subject: [PATCH 14/17] goodixtls: tolerate slow finger-detect response with retry Give finger-detect awaits their own 2000ms timeout and retry a timed out await once before failing the scan; a late reply simply arrives during the second await. Fast replies behave exactly as before. --- libfprint/drivers/goodixtls/goodix5e0a.c | 26 ++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c index 5240f8361..4cdb886d2 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.c +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -56,6 +56,7 @@ struct _FpiDeviceGoodixTls5e0a guint scan_gen; guint scan_timeout_gen; GSource *down_timeout; + gboolean down_retried; /* TLS session parking state across deactivate/activate cycles */ gboolean tls_parked; @@ -152,6 +153,9 @@ static void goodix5e0a_reset_touch_frames (FpiDeviceGoodixTls5e0a *self); #define GOODIX_5E0A_TLS_PARK_TTL_US (G_USEC_PER_SEC * 30) #define GOODIX_5E0A_TLS_PARK_HEALTH_TIMEOUT_MS 500 #define GOODIX_5E0A_WARM_TTL_US (G_USEC_PER_SEC * 60) +/* Finger-detect awaits take longer than other commands after a bus reset, + * so they get their own timeout plus a single retry before failing. */ +#define GOODIX_5E0A_FDT_TIMEOUT_MS 2000 enum activate_states { ACTIVATE_READ_AND_NOP, @@ -668,7 +672,7 @@ goodix5e0a_on_down_poll_timeout (FpDevice *dev, gpointer user_data) send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, goodix_5e0a_down_s12, sizeof (goodix_5e0a_down_s12), - GOODIX_TIMEOUT, goodix5e0a_on_fdt_down_reply, ssm); + GOODIX_5E0A_FDT_TIMEOUT_MS, goodix5e0a_on_fdt_down_reply, ssm); } static void @@ -687,10 +691,27 @@ goodix5e0a_on_fdt_down_reply (FpDevice *dev, guint8 *data, guint16 len, fpi_ssm_mark_failed (ssm, err); return; } + /* A slow finger-detect response is retried once before failing; the + * late reply to the first attempt simply arrives during the second. */ + if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_TIMED_OUT) && + !self->down_retried && self->scan_ssm == ssm) + { + self->down_retried = TRUE; + g_error_free (err); + fp_dbg ("finger-detect timed out, retrying once"); + send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, + goodix_5e0a_down_s12, sizeof (goodix_5e0a_down_s12), + GOODIX_5E0A_FDT_TIMEOUT_MS, + goodix5e0a_on_fdt_down_reply, ssm); + return; + } fpi_ssm_mark_failed (ssm, err); return; } + /* A completed await re-arms the single-retry budget for the next one. */ + self->down_retried = FALSE; + status = (len > 0) ? data[0] : 0x00; fp_dbg ("finger-detect reply: status=0x%02x len=%u", status, len); @@ -1042,9 +1063,10 @@ goodix5e0a_scan_run_state (FpiSsm *ssm, FpDevice *dev) break; case SCAN_5E0A_FDT_DOWN: + self->down_retried = FALSE; send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, goodix_5e0a_down_s12, sizeof (goodix_5e0a_down_s12), - GOODIX_TIMEOUT, goodix5e0a_on_fdt_down_reply, ssm); + GOODIX_5E0A_FDT_TIMEOUT_MS, goodix5e0a_on_fdt_down_reply, ssm); break; case SCAN_5E0A_GET_IMAGE: From fa500f7d5d3990f1f3dda93964e3589aaa9736d1 Mon Sep 17 00:00:00 2001 From: Nix User Date: Thu, 10 Sep 2026 17:07:28 +0530 Subject: [PATCH 15/17] goodixtls: review follow-ups for finger-detect retry Soften two comments to stated facts, keep the error text in the retry debug line. --- libfprint/drivers/goodixtls/goodix5e0a.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c index 4cdb886d2..f02f21ce5 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.c +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -153,8 +153,8 @@ static void goodix5e0a_reset_touch_frames (FpiDeviceGoodixTls5e0a *self); #define GOODIX_5E0A_TLS_PARK_TTL_US (G_USEC_PER_SEC * 30) #define GOODIX_5E0A_TLS_PARK_HEALTH_TIMEOUT_MS 500 #define GOODIX_5E0A_WARM_TTL_US (G_USEC_PER_SEC * 60) -/* Finger-detect awaits take longer than other commands after a bus reset, - * so they get their own timeout plus a single retry before failing. */ +/* Finger-detect awaits can take longer than other commands, so they get + * their own timeout plus a single retry before failing. */ #define GOODIX_5E0A_FDT_TIMEOUT_MS 2000 enum activate_states { @@ -691,14 +691,13 @@ goodix5e0a_on_fdt_down_reply (FpDevice *dev, guint8 *data, guint16 len, fpi_ssm_mark_failed (ssm, err); return; } - /* A slow finger-detect response is retried once before failing; the - * late reply to the first attempt simply arrives during the second. */ + /* A slow finger-detect response is retried once before failing. */ if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_TIMED_OUT) && !self->down_retried && self->scan_ssm == ssm) { self->down_retried = TRUE; + fp_dbg ("finger-detect timed out (%s), retrying once", err->message); g_error_free (err); - fp_dbg ("finger-detect timed out, retrying once"); send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, goodix_5e0a_down_s12, sizeof (goodix_5e0a_down_s12), GOODIX_5E0A_FDT_TIMEOUT_MS, @@ -709,7 +708,7 @@ goodix5e0a_on_fdt_down_reply (FpDevice *dev, guint8 *data, guint16 len, return; } - /* A completed await re-arms the single-retry budget for the next one. */ + /* Reset the retry flag on every completed await. */ self->down_retried = FALSE; status = (len > 0) ? data[0] : 0x00; From 1da3a0fd88196b87e7ad261800b5e215bc24f2a7 Mon Sep 17 00:00:00 2001 From: Nix User Date: Thu, 10 Sep 2026 17:35:49 +0530 Subject: [PATCH 16/17] fix: 0x32 timeout --- libfprint/drivers/goodixtls/goodix5e0a.c | 29 +++++------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c index f02f21ce5..0accb6ecc 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.c +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -56,7 +56,6 @@ struct _FpiDeviceGoodixTls5e0a guint scan_gen; guint scan_timeout_gen; GSource *down_timeout; - gboolean down_retried; /* TLS session parking state across deactivate/activate cycles */ gboolean tls_parked; @@ -153,9 +152,6 @@ static void goodix5e0a_reset_touch_frames (FpiDeviceGoodixTls5e0a *self); #define GOODIX_5E0A_TLS_PARK_TTL_US (G_USEC_PER_SEC * 30) #define GOODIX_5E0A_TLS_PARK_HEALTH_TIMEOUT_MS 500 #define GOODIX_5E0A_WARM_TTL_US (G_USEC_PER_SEC * 60) -/* Finger-detect awaits can take longer than other commands, so they get - * their own timeout plus a single retry before failing. */ -#define GOODIX_5E0A_FDT_TIMEOUT_MS 2000 enum activate_states { ACTIVATE_READ_AND_NOP, @@ -672,7 +668,7 @@ goodix5e0a_on_down_poll_timeout (FpDevice *dev, gpointer user_data) send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, goodix_5e0a_down_s12, sizeof (goodix_5e0a_down_s12), - GOODIX_5E0A_FDT_TIMEOUT_MS, goodix5e0a_on_fdt_down_reply, ssm); + 0, goodix5e0a_on_fdt_down_reply, ssm); } static void @@ -691,26 +687,10 @@ goodix5e0a_on_fdt_down_reply (FpDevice *dev, guint8 *data, guint16 len, fpi_ssm_mark_failed (ssm, err); return; } - /* A slow finger-detect response is retried once before failing. */ - if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_TIMED_OUT) && - !self->down_retried && self->scan_ssm == ssm) - { - self->down_retried = TRUE; - fp_dbg ("finger-detect timed out (%s), retrying once", err->message); - g_error_free (err); - send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, - goodix_5e0a_down_s12, sizeof (goodix_5e0a_down_s12), - GOODIX_5E0A_FDT_TIMEOUT_MS, - goodix5e0a_on_fdt_down_reply, ssm); - return; - } fpi_ssm_mark_failed (ssm, err); return; } - /* Reset the retry flag on every completed await. */ - self->down_retried = FALSE; - status = (len > 0) ? data[0] : 0x00; fp_dbg ("finger-detect reply: status=0x%02x len=%u", status, len); @@ -1062,10 +1042,13 @@ goodix5e0a_scan_run_state (FpiSsm *ssm, FpDevice *dev) break; case SCAN_5E0A_FDT_DOWN: - self->down_retried = FALSE; + /* Blocking wait for the MCU capacitive touch interrupt: timeout 0 + * installs no libfprint timer, so the await survives until a real + * touch, client cancel, or deactivate. Any finite timeout turns an + * idle wait into "Command timed out: 0x32". */ send_cmd_reply (dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, goodix_5e0a_down_s12, sizeof (goodix_5e0a_down_s12), - GOODIX_5E0A_FDT_TIMEOUT_MS, goodix5e0a_on_fdt_down_reply, ssm); + 0, goodix5e0a_on_fdt_down_reply, ssm); break; case SCAN_5E0A_GET_IMAGE: From 8ab64e08213898af2714992e4439d7bcff180ba6 Mon Sep 17 00:00:00 2001 From: Nix User Date: Thu, 10 Sep 2026 19:32:18 +0530 Subject: [PATCH 17/17] fix: preserve retry guard --- libfprint/drivers/goodixtls/goodix5e0a.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libfprint/drivers/goodixtls/goodix5e0a.c b/libfprint/drivers/goodixtls/goodix5e0a.c index 0accb6ecc..8645db3e7 100644 --- a/libfprint/drivers/goodixtls/goodix5e0a.c +++ b/libfprint/drivers/goodixtls/goodix5e0a.c @@ -1153,8 +1153,6 @@ goodix5e0a_deactivate (FpImageDevice *img_dev) self->session_started = FALSE; self->scan_gen++; - self->retry_guard = FALSE; - self->retry_guard_mono = 0; if (self->down_timeout) { g_source_destroy (self->down_timeout); @@ -1186,6 +1184,8 @@ goodix5e0a_deactivate (FpImageDevice *img_dev) } self->tls_parked = FALSE; + self->retry_guard = FALSE; + self->retry_guard_mono = 0; goodix_session_mark_dirty (dev); goodix_shutdown_tls (dev, &tls_err); goodix_stop_read_loop (dev);