diff --git a/packages/patched-derivations.nix b/packages/patched-derivations.nix index 785f870..d97f809 100644 --- a/packages/patched-derivations.nix +++ b/packages/patched-derivations.nix @@ -13,6 +13,14 @@ super: rec { gnome-control-center = patch' super.gnome.gnome-control-center; + libfprint = (patch' super.libfprint).overrideAttrs (old: { + buildInputs = old.buildInputs ++ [ + super.openssl + ]; + }); + + fprintd = super.fprintd.override { inherit libfprint; }; + nautilus = (patch' super.gnome.nautilus).overrideAttrs (attrs: { preFixup = with super; let py = (python3.withPackages (ps: with ps; [ ps.pygobject3 ])); diff --git a/patches/base/libfprint/532d_support.patch b/patches/base/libfprint/532d_support.patch new file mode 100644 index 0000000..83604d4 --- /dev/null +++ b/patches/base/libfprint/532d_support.patch @@ -0,0 +1,5028 @@ +diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml +new file mode 100644 +index 00000000..4997f6f1 +--- /dev/null ++++ b/.github/FUNDING.yml +@@ -0,0 +1,2 @@ ++github: mpi3d ++custom: "btc.com/btc/address/1MPi3D1HxS71k83XnqK3yB8CNmyHQeerZZ" +diff --git a/.gitignore b/.gitignore +index 07d73995..0ab23946 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,4 @@ + *.o + *.swp + _build ++*.pgm +diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json +new file mode 100644 +index 00000000..6994aa63 +--- /dev/null ++++ b/.vscode/c_cpp_properties.json +@@ -0,0 +1,17 @@ ++{ ++ "version": 4, ++ "configurations": [ ++ { ++ "name": "Linux", ++ "includePath": [ ++ "${workspaceFolder}/**" ++ ], ++ "defines": [], ++ "compilerPath": "/usr/bin/gcc", ++ "cStandard": "gnu17", ++ "cppStandard": "gnu++14", ++ "intelliSenseMode": "linux-gcc-x64", ++ "compileCommands": "${workspaceFolder}/_build/compile_commands.json" ++ } ++ ] ++} +\ No newline at end of file +diff --git a/.vscode/launch.json b/.vscode/launch.json +new file mode 100644 +index 00000000..68646c13 +--- /dev/null ++++ b/.vscode/launch.json +@@ -0,0 +1,33 @@ ++{ ++ // Use IntelliSense to learn about possible attributes. ++ // Hover to view descriptions of existing attributes. ++ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 ++ "version": "0.2.0", ++ "configurations": [ ++ { ++ "name": "Launch Image Capture", ++ "type": "cppdbg", ++ "request": "launch", ++ "program": "${workspaceFolder}/_build/examples/img-capture", ++ "args": [], ++ "stopAtEntry": false, ++ "cwd": "${fileDirname}", ++ "environment": [], ++ "externalConsole": false, ++ "MIMode": "gdb", ++ "setupCommands": [ ++ { ++ "description": "Enable pretty-printing for gdb", ++ "text": "-enable-pretty-printing", ++ "ignoreFailures": true ++ }, ++ { ++ "description": "Set Disassembly Flavor to Intel", ++ "text": "-gdb-set disassembly-flavor intel", ++ "ignoreFailures": true ++ } ++ ], ++ "preLaunchTask": "${defaultBuildTask}" ++ } ++ ] ++} +\ No newline at end of file +diff --git a/.vscode/settings.json b/.vscode/settings.json +new file mode 100644 +index 00000000..0e6ce268 +--- /dev/null ++++ b/.vscode/settings.json +@@ -0,0 +1,15 @@ ++{ ++ "files.associations": { ++ "*.c": "c", ++ "*.h": "c", ++ "limits": "c", ++ "array": "c", ++ "string": "c", ++ "string_view": "c", ++ "ranges": "c", ++ "new": "c", ++ "streambuf": "c", ++ "functional": "c", ++ "typeinfo": "c" ++ } ++} +diff --git a/.vscode/tasks.json b/.vscode/tasks.json +new file mode 100644 +index 00000000..6f515c8c +--- /dev/null ++++ b/.vscode/tasks.json +@@ -0,0 +1,36 @@ ++{ ++ "version": "2.0.0", ++ "tasks": [ ++ { ++ "type": "shell", ++ "label": "Configure the project", ++ "command": "/usr/bin/meson", ++ "args": [ ++ "${workspaceFolder}/_build" ++ ], ++ "problemMatcher": [] ++ }, ++ { ++ "type": "shell", ++ "label": "Build the project", ++ "command": "/usr/bin/meson", ++ "args": [ ++ "compile" ++ ], ++ "options": { ++ "cwd": "${workspaceFolder}/_build" ++ }, ++ "problemMatcher": { ++ "base": "$gcc", ++ "fileLocation": [ ++ "relative", ++ "${workspaceFolder}/_build" ++ ] ++ }, ++ "group": { ++ "kind": "build", ++ "isDefault": true ++ } ++ } ++ ] ++} +\ No newline at end of file +diff --git a/README.md b/README.md +index 7f59b4e8..68f18953 100644 +--- a/README.md ++++ b/README.md +@@ -1,3 +1,8 @@ ++This is an experimental libfprint driver implementation for Goodix drivers. ++ ++Currently in the works: ++- 27c6x5110 (80x64 resolution) ++ + # libfprint + + libfprint is part of the fprint project: +diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb +index 6e3fd396..9862d188 100644 +--- a/data/autosuspend.hwdb ++++ b/data/autosuspend.hwdb +@@ -146,6 +146,7 @@ usb:v04F3p0C58* + usb:v04F3p0C7D* + usb:v04F3p0C7E* + usb:v04F3p0C82* ++usb:v04F3p0C88* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + +@@ -172,6 +173,17 @@ usb:v27C6p6A94* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + ++# Supported by libfprint driver goodixtls511 ++usb:v27C6p5110* ++ ID_AUTOSUSPEND=1 ++ ID_PERSIST=0 ++ ++# Supported by libfprint driver goodixtls53xd ++usb:v27C6p538D* ++usb:v27C6p532D* ++ ID_AUTOSUSPEND=1 ++ ID_PERSIST=0 ++ + # Supported by libfprint driver nb1010 + usb:v298Dp1010* + ID_AUTOSUSPEND=1 +@@ -317,19 +329,16 @@ usb:v1C7Ap0300* + usb:v1C7Ap0575* + usb:v1C7Ap0576* + usb:v27C6p5042* +-usb:v27C6p5110* + usb:v27C6p5117* + usb:v27C6p5125* + usb:v27C6p5201* + usb:v27C6p521D* + usb:v27C6p5301* + usb:v27C6p530C* +-usb:v27C6p532D* + usb:v27C6p533C* + usb:v27C6p5381* + usb:v27C6p5385* + usb:v27C6p538C* +-usb:v27C6p538D* + usb:v27C6p5395* + usb:v27C6p5503* + usb:v27C6p5584* +diff --git a/libfprint/drivers/elanmoc/elanmoc.c b/libfprint/drivers/elanmoc/elanmoc.c +index 3185ee7a..ad23e93e 100644 +--- a/libfprint/drivers/elanmoc/elanmoc.c ++++ b/libfprint/drivers/elanmoc/elanmoc.c +@@ -28,6 +28,7 @@ static const FpIdEntry id_table[] = { + { .vid = 0x04f3, .pid = 0x0c7d, }, + { .vid = 0x04f3, .pid = 0x0c7e, }, + { .vid = 0x04f3, .pid = 0x0c82, }, ++ { .vid = 0x04f3, .pid = 0x0c88, }, + { .vid = 0, .pid = 0, .driver_data = 0 }, /* terminating entry */ + }; + +diff --git a/libfprint/drivers/goodixmoc/goodix.c b/libfprint/drivers/goodixmoc/goodix.c +index 4eeb7215..cfc69c94 100644 +--- a/libfprint/drivers/goodixmoc/goodix.c ++++ b/libfprint/drivers/goodixmoc/goodix.c +@@ -661,7 +661,7 @@ fp_enroll_capture_cb (FpiDeviceGoodixMoc *self, + /* */ + if (resp->result >= GX_FAILED) + { +- fp_warn ("Capture sample failed, result: 0x%x", resp->result); ++ fp_info ("Capture sample failed, result: 0x%x", resp->result); + fpi_device_enroll_progress (FP_DEVICE (self), + self->enroll_stage, + NULL, +@@ -675,7 +675,7 @@ fp_enroll_capture_cb (FpiDeviceGoodixMoc *self, + if ((resp->capture_data_resp.img_quality < self->sensorcfg->config[4]) || + (resp->capture_data_resp.img_coverage < self->sensorcfg->config[5])) + { +- fp_warn ("Capture sample poor quality(%d): %d or coverage(%d): %d", ++ fp_info ("Capture sample poor quality(%d): %d or coverage(%d): %d", + self->sensorcfg->config[4], + resp->capture_data_resp.img_quality, + self->sensorcfg->config[5], +@@ -1041,6 +1041,47 @@ fp_init_config_cb (FpiDeviceGoodixMoc *self, + fpi_ssm_next_state (self->task_ssm); + } + ++static void ++fp_init_cb_reset_or_complete (FpiDeviceGoodixMoc *self, ++ gxfp_cmd_response_t *resp, ++ GError *error) ++{ ++ if (error) ++ { ++ fp_warn ("Template storage appears to have been corrupted! Error was: %s", error->message); ++ fp_warn ("A known reason for this to happen is a firmware bug triggered by another storage area being initialized."); ++ fpi_ssm_jump_to_state (self->task_ssm, FP_INIT_RESET_DEVICE); ++ } ++ else ++ { ++ fpi_ssm_mark_completed (self->task_ssm); ++ } ++} ++ ++static void ++fp_init_reset_device_cb (FpiDeviceGoodixMoc *self, ++ gxfp_cmd_response_t *resp, ++ GError *error) ++{ ++ if (error) ++ { ++ fp_warn ("Reset failed: %s", error->message); ++ fpi_ssm_mark_failed (self->task_ssm, error); ++ return; ++ } ++ if ((resp->result >= GX_FAILED) && (resp->result != GX_ERROR_FINGER_ID_NOEXIST)) ++ { ++ fp_warn ("Reset failed, device reported: 0x%x", resp->result); ++ fpi_ssm_mark_failed (self->task_ssm, ++ fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, ++ "Failed clear storage, result: 0x%x", ++ resp->result)); ++ return; ++ } ++ ++ fp_warn ("Reset completed"); ++ fpi_ssm_mark_completed (self->task_ssm); ++} + + static void + fp_init_sm_run_state (FpiSsm *ssm, FpDevice *device) +@@ -1065,6 +1106,30 @@ fp_init_sm_run_state (FpiSsm *ssm, FpDevice *device) + sizeof (gxfp_sensor_cfg_t), + fp_init_config_cb); + break; ++ ++ case FP_INIT_TEMPLATE_LIST: ++ /* List prints to check whether the template DB was corrupted. ++ * As of 2022-06-13 there is a known firmware issue that can cause the ++ * stored templates for Linux to be corrupted when the Windows storage ++ * area is initialized. ++ * In that case, we'll get a protocol failure trying to retrieve the ++ * list of prints. ++ */ ++ goodix_sensor_cmd (self, MOC_CMD0_GETFINGERLIST, MOC_CMD1_DEFAULT, ++ FALSE, ++ (const guint8 *) &dummy, ++ 1, ++ fp_init_cb_reset_or_complete); ++ break; ++ ++ case FP_INIT_RESET_DEVICE: ++ fp_warn ("Resetting device storage, you will need to enroll all prints again!"); ++ goodix_sensor_cmd (self, MOC_CMD0_DELETETEMPLATE, MOC_CMD1_DELETE_ALL, ++ FALSE, ++ NULL, ++ 0, ++ fp_init_reset_device_cb); ++ break; + } + + +diff --git a/libfprint/drivers/goodixmoc/goodix.h b/libfprint/drivers/goodixmoc/goodix.h +index 23e142ac..56b2d171 100644 +--- a/libfprint/drivers/goodixmoc/goodix.h ++++ b/libfprint/drivers/goodixmoc/goodix.h +@@ -35,6 +35,8 @@ typedef enum { + typedef enum { + FP_INIT_VERSION = 0, + FP_INIT_CONFIG, ++ FP_INIT_TEMPLATE_LIST, ++ FP_INIT_RESET_DEVICE, + FP_INIT_NUM_STATES, + } FpInitState; + +diff --git a/libfprint/drivers/goodixmoc/goodix_proto.c b/libfprint/drivers/goodixmoc/goodix_proto.c +index b615dbaa..72511a88 100644 +--- a/libfprint/drivers/goodixmoc/goodix_proto.c ++++ b/libfprint/drivers/goodixmoc/goodix_proto.c +@@ -393,10 +393,8 @@ gx_proto_parse_body (uint16_t cmd, uint8_t *buffer, uint16_t buffer_len, pgxfp_c + fingerid_length, + &presp->finger_list_resp.finger_list[num]) != 0) + { +- g_error ("parse fingerlist error"); +- presp->finger_list_resp.finger_num = 0; +- presp->result = GX_FAILED; +- break; ++ g_warning ("Failed to parse finger list"); ++ return -1; + } + offset += fingerid_length; + } +diff --git a/libfprint/drivers/goodixtls/goodix.c b/libfprint/drivers/goodixtls/goodix.c +new file mode 100644 +index 00000000..0d2f0b7f +--- /dev/null ++++ b/libfprint/drivers/goodixtls/goodix.c +@@ -0,0 +1,1448 @@ ++// Goodix Tls driver for libfprint ++ ++// Copyright (C) 2021 Alexander Meiler ++// Copyright (C) 2021 Matthieu CHARETTE ++ ++// 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; ++ ++ 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++) ++ sprintf(string + i * 2, "%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)) ++ return; ++ ++ goodix_reset_state(dev); ++ if (!error) ++ fp_dbg("Completed command: 0x%02x", priv->cmd); ++ ++ if (callback) ++ callback(dev, data, length, user_data, 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_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; ++ } ++ ++ GoodixPresetPskResponse *response = (GoodixPresetPskResponse*)(data + sizeof(guint8)); ++ psk_len = response->length; ++ if (length < psk_len + sizeof(guint8) + sizeof(GoodixPresetPskResponse)) ++ { ++ 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(response->flags), ++ data + sizeof(guint8) + sizeof(GoodixPresetPskResponse), 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 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); ++ ++ 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: ++ fp_dbg("Got TLS msg"); ++ goodix_receive_done(dev, payload, payload_len, NULL); ++ ++ // TLS message sending it to TLS server. ++ // TODO ++ break; ++ ++ case GOODIX_FLAGS_TLS_DATA: ++ fp_dbg("Got TLS data msg"); ++ // GOODIX 52xd: Remove first 9 to get valid TLS content ++ goodix_receive_done(dev, payload+9, payload_len-9, NULL); ++ 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)) ++ { ++ fp_dbg("transfer cancelled, aborting read loop..."); ++ 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) ++ { ++ // Already going ++ return; ++ } ++ else ++ { ++ priv->inited = TRUE; ++ } ++ if (g_cancellable_is_cancelled(priv->transfer_cancel_tkn)) ++ { ++ g_cancellable_reset(priv->transfer_cancel_tkn); ++ } ++ ++ goodix_receive_data(dev); ++} ++ ++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, 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(payload); ++ 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(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; ++ ++ 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_TIMEOUT, FALSE, ++ goodix_receive_none, cb_info); ++ goodix_receive_done(dev, NULL, 0, NULL); ++ return; ++ } ++ ++ goodix_send_protocol(dev, GOODIX_CMD_NOP, (guint8 *)&payload, sizeof(payload), ++ NULL, FALSE, GOODIX_TIMEOUT, FALSE, NULL, NULL); ++ goodix_receive_done(dev, NULL, 0, NULL); ++} ++ ++void goodix_send_mcu_get_image(FpDevice *dev, GoodixImageCallback 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_MCU_GET_IMAGE, (guint8 *)&payload, ++ sizeof(payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, ++ goodix_receive_default, cb_info); ++ return; ++ } ++ ++ goodix_send_protocol(dev, GOODIX_CMD_MCU_GET_IMAGE, (guint8 *)&payload, ++ sizeof(payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, ++ NULL, NULL); ++} ++ ++void goodix_send_mcu_switch_to_fdt_down(FpDevice *dev, guint8 *mode, ++ guint16 length, ++ GDestroyNotify free_func, ++ GoodixDefaultCallback 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_MCU_SWITCH_TO_FDT_DOWN, mode, length, ++ free_func, TRUE, 0, TRUE, goodix_receive_default, ++ cb_info); ++ return; ++ } ++ ++ goodix_send_protocol(dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, mode, length, ++ free_func, TRUE, 0, TRUE, NULL, NULL); ++} ++ ++void goodix_send_mcu_switch_to_fdt_up(FpDevice *dev, guint8 *mode, ++ guint16 length, GDestroyNotify free_func, ++ GoodixDefaultCallback 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_MCU_SWITCH_TO_FDT_UP, mode, length, ++ free_func, TRUE, 0, TRUE, goodix_receive_default, ++ cb_info); ++ return; ++ } ++ ++ goodix_send_protocol(dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_UP, mode, length, ++ free_func, TRUE, 0, TRUE, NULL, NULL); ++} ++ ++void goodix_send_mcu_switch_to_fdt_mode(FpDevice *dev, guint8 *mode, ++ guint16 length, ++ GDestroyNotify free_func, ++ GoodixDefaultCallback 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_MCU_SWITCH_TO_FDT_MODE, mode, length, ++ free_func, TRUE, 0, TRUE, goodix_receive_default, ++ cb_info); ++ return; ++ } ++ ++ goodix_send_protocol(dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_MODE, mode, length, ++ free_func, TRUE, 0, TRUE, NULL, NULL); ++} ++ ++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_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, GoodixDefaultCallback 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, TRUE, ++ goodix_receive_default, cb_info); ++ return; ++ } ++ ++ goodix_send_protocol(dev, GOODIX_CMD_QUERY_MCU_STATE, (guint8 *)&payload, ++ sizeof(payload), NULL, TRUE, GOODIX_TIMEOUT, TRUE, 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; ++ ++ goodix_send_protocol(dev, GOODIX_CMD_TLS_SUCCESSFULLY_ESTABLISHED, ++ (guint8 *)&payload, sizeof(payload), NULL, TRUE, ++ GOODIX_TIMEOUT, FALSE, goodix_receive_none, cb_info); ++ return; ++ } ++ ++ goodix_send_protocol(dev, GOODIX_CMD_TLS_SUCCESSFULLY_ESTABLISHED, ++ (guint8 *)&payload, sizeof(payload), NULL, TRUE, ++ GOODIX_TIMEOUT, FALSE, NULL, NULL); ++} ++ ++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(payload) + 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(payload) + 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(); ++ ++ 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->callback = NULL; ++ priv->user_data = NULL; ++} ++ ++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); ++ ++ 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_server_ready(GoodixTlsServer *server, GError *err, ++ gpointer dev) ++{ ++ if (err) ++ { ++ fp_err("server ready failed: %s", err->message); ++ return; ++ } ++ fp_dbg("TLS connection ready"); ++} ++ ++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_send(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); ++} ++static void tls_handshake_done(FpiSsm *ssm, FpDevice *dev, GError *error) ++{ ++ if (error) ++ { ++ fp_dbg("failed to do tls handshake: %s (code: %d)", error->message, ++ error->code); ++ } ++ 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_recv(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_recv(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_send(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_complete(FpiSsm *ssm, FpDevice *dev, GError *error) ++{ ++ fpi_image_device_activate_complete(FP_IMAGE_DEVICE(dev), error); ++} ++ ++void goodix_tls(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 = malloc(sizeof(GoodixTlsServer)); ++ ++ if (!priv->tls_ready_callback) ++ { ++ priv->tls_ready_callback = malloc(sizeof(GoodixCallbackInfo)); ++ } ++ priv->tls_ready_callback->callback = G_CALLBACK(callback); ++ priv->tls_ready_callback->user_data = user_data; ++ GoodixTlsServer *s = priv->tls_hop; ++ s->connection_callback = on_goodix_tls_server_ready; ++ 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; ++} ++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); ++ goodix_tls_client_send(priv->tls_hop, data, length); ++ ++ const guint16 size = -1; ++ guint8 *buff = malloc(size); ++ GError *err = NULL; ++ int read_size = goodix_tls_server_receive(priv->tls_hop, buff, size, &err); ++ if (read_size <= 0) ++ { ++ callback(dev, NULL, 0, cb_info->user_data, err); ++ g_free(cb_info); ++ return; ++ } ++ ++ callback(dev, buff, read_size, cb_info->user_data, NULL); ++ 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 00000000..208f58eb +--- /dev/null ++++ b/libfprint/drivers/goodixtls/goodix.h +@@ -0,0 +1,260 @@ ++// Goodix Tls driver for libfprint ++ ++// Copyright (C) 2021 Alexander Meiler ++// Copyright (C) 2021 Matthieu CHARETTE ++ ++// 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 ++ ++// 1 seconds USB timeout ++#define GOODIX_TIMEOUT (1000) ++ ++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 ---- ++ ++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_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); ++ ++void goodix_start_read_loop(FpDevice *dev); ++// ---- GOODIX RECEIVE SECTION END ---- ++ ++// ----------------------------------------------------------------------------- ++ ++// ---- GOODIX SEND SECTION START ---- ++ ++gboolean goodix_send_data(FpDevice *dev, guint8 *data, guint32 length, ++ GDestroyNotify free_func, GError **error); ++ ++gboolean goodix_send_pack(FpDevice *dev, guint8 flags, guint8 *payload, ++ guint16 length, GDestroyNotify free_func, ++ GError **error); ++ ++void goodix_send_protocol(FpDevice *dev, guint8 cmd, guint8 *payload, ++ guint16 length, GDestroyNotify free_func, ++ gboolean calc_checksum, guint timeout_ms, ++ gboolean reply, GoodixCmdCallback callback, ++ gpointer user_data); ++ ++void goodix_send_nop(FpDevice *dev, GoodixNoneCallback callback, ++ gpointer user_data); ++ ++void goodix_send_mcu_get_image(FpDevice *dev, GoodixImageCallback callback, ++ gpointer user_data); ++ ++void goodix_send_mcu_switch_to_fdt_down(FpDevice *dev, guint8 *mode, ++ guint16 length, ++ GDestroyNotify free_func, ++ GoodixDefaultCallback callback, ++ gpointer user_data); ++ ++void goodix_send_mcu_switch_to_fdt_up(FpDevice *dev, guint8 *mode, ++ guint16 length, GDestroyNotify free_func, ++ GoodixDefaultCallback callback, ++ gpointer user_data); ++ ++void goodix_send_mcu_switch_to_fdt_mode(FpDevice *dev, guint8 *mode, ++ guint16 length, ++ GDestroyNotify free_func, ++ GoodixDefaultCallback 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); ++ ++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); ++ ++void goodix_send_enable_chip(FpDevice *dev, gboolean enable, ++ GoodixNoneCallback callback, gpointer user_data); ++ ++void goodix_send_reset(FpDevice *dev, gboolean reset_sensor, guint8 sleep_time, ++ GoodixResetCallback callback, gpointer user_data); ++ ++void goodix_send_firmware_version(FpDevice *dev, ++ GoodixFirmwareVersionCallback callback, ++ gpointer user_data); ++ ++void goodix_send_query_mcu_state(FpDevice *dev, GoodixDefaultCallback callback, ++ gpointer user_data); ++ ++void goodix_send_request_tls_connection(FpDevice *dev, ++ GoodixDefaultCallback callback, ++ gpointer user_data); ++ ++void goodix_send_tls_successfully_established(FpDevice *dev, ++ GoodixNoneCallback callback, ++ gpointer user_data); ++ ++void goodix_send_preset_psk_write(FpDevice *dev, guint32 flags, guint8 *psk, ++ guint16 length, GDestroyNotify free_func, ++ GoodixSuccessCallback callback, ++ gpointer user_data); ++ ++void goodix_send_preset_psk_read(FpDevice *dev, guint32 flags, guint16 length, ++ GoodixPresetPskReadCallback callback, ++ gpointer user_data); ++ ++void goodix_send_read_otp(FpDevice *dev, GoodixDefaultCallback callback, ++ gpointer user_data); ++ ++// ---- GOODIX SEND SECTION END ---- ++ ++// ----------------------------------------------------------------------------- ++ ++// ---- DEV SECTION START ---- ++ ++gboolean goodix_dev_init(FpDevice *dev, GError **error); ++ ++gboolean goodix_dev_deinit(FpDevice *dev, GError **error); ++ ++void goodix_reset_state(FpDevice *dev); ++ ++// ---- DEV SECTION END ---- ++ ++// ----------------------------------------------------------------------------- ++ ++// ---- TLS SECTION START ---- ++ ++void goodix_read_tls(FpDevice *dev, GoodixTlsCallback callback, ++ gpointer user_data); ++ ++void goodix_tls_run_state(FpiSsm *ssm, FpDevice *dev); ++ ++void goodix_tls_complete(FpiSsm *ssm, FpDevice *dev, GError *error); ++ ++void goodix_tls(FpDevice *dev, GoodixNoneCallback callback, gpointer user_data); ++ ++gboolean goodix_shutdown_tls(FpDevice *dev, GError **error); ++ ++void goodix_tls_ready_image_handler(FpDevice *dev, guint8 *data, ++ guint16 length, gpointer user_data, ++ GError *error); ++ ++void goodix_tls_read_image(FpDevice *dev, GoodixImageCallback callback, ++ gpointer user_data); ++ ++void goodix_tls_decrypt_image(FpDevice *dev, guint8 **data, guint16 *len); ++ ++// ---- TLS SECTION END ---- +diff --git a/libfprint/drivers/goodixtls/goodix511.c b/libfprint/drivers/goodixtls/goodix511.c +new file mode 100644 +index 00000000..cbefe218 +--- /dev/null ++++ b/libfprint/drivers/goodixtls/goodix511.c +@@ -0,0 +1,898 @@ ++// Goodix Tls driver for libfprint ++ ++// Copyright (C) 2021 Alexander Meiler ++// Copyright (C) 2021 Matthieu CHARETTE ++ ++// 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-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 ++#define FP_COMPONENT "goodixtls511" ++ ++#include ++#include ++ ++#include "drivers_api.h" ++#include "goodix.h" ++#include "goodix_proto.h" ++#include "goodix511.h" ++ ++#include ++ ++#define GOODIX511_WIDTH 64 ++#define GOODIX511_HEIGHT 80 ++#define GOODIX511_SCAN_WIDTH 88 ++#define GOODIX511_FRAME_SIZE (GOODIX511_WIDTH * GOODIX511_HEIGHT) ++// For every 4 pixels there are 6 bytes and there are 8 extra start bytes and 5 ++// extra end ++#define GOODIX511_RAW_FRAME_SIZE \ ++ 8 + (GOODIX511_HEIGHT * GOODIX511_SCAN_WIDTH) / 4 * 6 + 5 ++#define GOODIX511_CAP_FRAMES 40 // Number of frames we capture per swipe ++ ++typedef unsigned short Goodix511Pix; ++ ++struct _FpiDeviceGoodixTls511 ++{ ++ FpiDeviceGoodixTls parent; ++ ++ guint8 *otp; ++ ++ GSList *frames; ++ ++ Goodix511Pix empty_img[GOODIX511_FRAME_SIZE]; ++}; ++ ++G_DECLARE_FINAL_TYPE(FpiDeviceGoodixTls511, fpi_device_goodixtls511, FPI, ++ DEVICE_GOODIXTLS511, FpiDeviceGoodixTls); ++ ++G_DEFINE_TYPE(FpiDeviceGoodixTls511, fpi_device_goodixtls511, ++ FPI_TYPE_DEVICE_GOODIXTLS); ++ ++// ---- ACTIVE SECTION START ---- ++ ++enum activate_states ++{ ++ ACTIVATE_READ_AND_NOP, ++ ACTIVATE_ENABLE_CHIP, ++ ACTIVATE_NOP, ++ ACTIVATE_CHECK_FW_VER, ++ ACTIVATE_CHECK_PSK, ++ ACTIVATE_RESET, ++ ACTIVATE_SET_MCU_IDLE, ++ ACTIVATE_SET_ODP, ++ ACTIVATE_SET_MCU_CONFIG, ++ ACTIVATE_SET_POWERDOWN_SCAN_FREQUENCY, ++ ACTIVATE_NUM_STATES, ++}; ++ ++#ifdef GOODIX511_DUMP_FRAMES ++static gboolean save_image_to_pgm(FpImage *img, const char *path) ++{ ++ FILE *fd = fopen(path, "w"); ++ size_t write_size; ++ const guchar *data = fp_image_get_data(img, &write_size); ++ int r; ++ ++ if (!fd) ++ { ++ g_warning("could not open '%s' for writing: %d", path, errno); ++ return FALSE; ++ } ++ ++ r = fprintf(fd, "P5 %d %d 255\n", fp_image_get_width(img), ++ fp_image_get_height(img)); ++ if (r < 0) ++ { ++ fclose(fd); ++ g_critical("pgm header write failed, error %d", r); ++ return FALSE; ++ } ++ ++ r = fwrite(data, 1, write_size, fd); ++ if (r < write_size) ++ { ++ fclose(fd); ++ g_critical("short write (%d)", r); ++ return FALSE; ++ } ++ ++ fclose(fd); ++ g_debug("written to '%s'", path); ++ ++ return TRUE; ++} ++#endif ++ ++static void 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); ++} ++ ++static void 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); ++ ++ if (strcmp(firmware, GOODIX_511_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); ++} ++ ++static void 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); ++ ++ if (number != GOODIX_511_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); ++} ++ ++static void 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); ++ ++ if (flags != GOODIX_511_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 != sizeof(goodix_511_psk_0)) ++ { ++ 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, goodix_511_psk_0, sizeof(goodix_511_psk_0))) ++ { ++ 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); ++} ++static void 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); ++} ++static void 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); ++ } ++} ++static void 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); ++ } ++} ++ ++enum otp_write_states ++{ ++ OTP_WRITE_1, ++ OTP_WRITE_2, ++ OTP_WRITE_3, ++ OTP_WRITE_4, ++ ++ OTP_WRITE_NUM, ++}; ++ ++static guint16 otp_write_addrs[] = {0x0220, 0x0236, 0x0238, 0x023a}; ++ ++static void otp_write_run(FpiSsm *ssm, FpDevice *dev) ++{ ++ guint16 data; ++ FpiDeviceGoodixTls511 *self = FPI_DEVICE_GOODIXTLS511(dev); ++ guint8 *otp = self->otp; ++ switch (fpi_ssm_get_cur_state(ssm)) ++ { ++ case OTP_WRITE_1: ++ data = otp[46] << 4 | 8; ++ break; ++ case OTP_WRITE_2: ++ data = otp[47]; ++ break; ++ case OTP_WRITE_3: ++ data = otp[48]; ++ break; ++ case OTP_WRITE_4: ++ data = otp[49]; ++ break; ++ } ++ ++ goodix_send_write_sensor_register( ++ dev, otp_write_addrs[fpi_ssm_get_cur_state(ssm)], data, check_none, ++ ssm); ++ if (fpi_ssm_get_cur_state(ssm) == OTP_WRITE_NUM - 1) ++ { ++ free(self->otp); ++ } ++} ++ ++static void read_otp_callback(FpDevice *dev, guint8 *data, guint16 len, ++ gpointer ssm, GError *err) ++{ ++ if (err) ++ { ++ fpi_ssm_mark_failed(ssm, err); ++ return; ++ } ++ if (len < 64) ++ { ++ fpi_ssm_mark_failed(ssm, g_error_new(FP_DEVICE_ERROR, ++ FP_DEVICE_ERROR_DATA_INVALID, ++ "OTP is invalid (len: %d)", 64)); ++ return; ++ } ++ FpiDeviceGoodixTls511 *self = FPI_DEVICE_GOODIXTLS511(dev); ++ self->otp = malloc(len); ++ memcpy(self->otp, data, len); ++ FpiSsm *otp_ssm = fpi_ssm_new(dev, otp_write_run, OTP_WRITE_NUM); ++ fpi_ssm_start_subsm(ssm, otp_ssm); ++} ++ ++static void activate_run_state(FpiSsm *ssm, FpDevice *dev) ++{ ++ ++ switch (fpi_ssm_get_cur_state(ssm)) ++ { ++ case ACTIVATE_READ_AND_NOP: ++ // Nop seems to clear the previous command buffer. But we are ++ // unable to do so. ++ goodix_start_read_loop(dev); ++ goodix_send_nop(dev, check_none, ssm); ++ break; ++ ++ case ACTIVATE_ENABLE_CHIP: ++ goodix_send_enable_chip(dev, TRUE, check_none, ssm); ++ break; ++ ++ case ACTIVATE_NOP: ++ goodix_send_nop(dev, check_none, ssm); ++ break; ++ ++ case ACTIVATE_CHECK_FW_VER: ++ goodix_send_firmware_version(dev, check_firmware_version, ssm); ++ break; ++ ++ case ACTIVATE_CHECK_PSK: ++ goodix_send_preset_psk_read(dev, GOODIX_511_PSK_FLAGS, 0, ++ check_preset_psk_read, ssm); ++ break; ++ ++ case ACTIVATE_RESET: ++ goodix_send_reset(dev, TRUE, 20, check_reset, ssm); ++ break; ++ ++ case ACTIVATE_SET_MCU_IDLE: ++ goodix_send_mcu_switch_to_idle_mode(dev, 20, check_idle, ssm); ++ break; ++ ++ case ACTIVATE_SET_ODP: ++ goodix_send_read_otp(dev, read_otp_callback, ssm); ++ break; ++ case ACTIVATE_SET_MCU_CONFIG: ++ goodix_send_upload_config_mcu(dev, goodix_511_config, ++ sizeof(goodix_511_config), NULL, ++ check_config_upload, ssm); ++ break; ++ ++ case ACTIVATE_SET_POWERDOWN_SCAN_FREQUENCY: ++ goodix_send_set_powerdown_scan_frequency( ++ dev, 100, check_powerdown_scan_freq, ssm); ++ break; ++ } ++} ++ ++static void tls_activation_complete(FpDevice *dev, gpointer user_data, ++ GError *error) ++{ ++ if (error) ++ { ++ fp_err("failed to complete tls activation: %s", error->message); ++ return; ++ } ++ FpImageDevice *image_dev = FP_IMAGE_DEVICE(dev); ++ ++ fpi_image_device_activate_complete(image_dev, error); ++} ++ ++static void activate_complete(FpiSsm *ssm, FpDevice *dev, GError *error) ++{ ++ G_DEBUG_HERE(); ++ if (!error) ++ goodix_tls(dev, tls_activation_complete, NULL); ++ else ++ { ++ fp_err("failed during activation: %s (code: %d)", error->message, ++ error->code); ++ fpi_image_device_activate_complete(FP_IMAGE_DEVICE(dev), error); ++ } ++} ++ ++// ---- ACTIVE SECTION END ---- ++ ++// ----------------------------------------------------------------------------- ++ ++// ---- SCAN SECTION START ---- ++ ++enum SCAN_STAGES ++{ ++ SCAN_STAGE_CALIBRATE, ++ SCAN_STAGE_SWITCH_TO_FDT_MODE, ++ SCAN_STAGE_SWITCH_TO_FDT_DOWN, ++ SCAN_STAGE_GET_IMG, ++ ++ SCAN_STAGE_NUM, ++}; ++ ++static void 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); ++} ++ ++static unsigned char get_pix(struct fpi_frame_asmbl_ctx *ctx, ++ struct fpi_frame *frame, unsigned int x, ++ unsigned int y) ++{ ++ return frame->data[x + y * GOODIX511_WIDTH]; ++} ++ ++// Bitdepth is 12, but we have to fit it in a byte ++static unsigned char squash(int v) { return v / 16; } ++ ++static void decode_frame(Goodix511Pix frame[GOODIX511_FRAME_SIZE], ++ const guint8 *raw_frame) ++{ ++ ++ Goodix511Pix uncropped[GOODIX511_SCAN_WIDTH * GOODIX511_HEIGHT]; ++ Goodix511Pix *pix = uncropped; ++ for (int i = 8; i != GOODIX511_RAW_FRAME_SIZE - 5; 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); ++ } ++ for (int y = 0; y != GOODIX511_HEIGHT; ++y) ++ { ++ for (int x = 0; x != GOODIX511_WIDTH; ++x) ++ { ++ const int idx = x + y * GOODIX511_SCAN_WIDTH; ++ frame[x + y * GOODIX511_WIDTH] = uncropped[idx]; ++ } ++ } ++} ++static int goodix_cmp_short(const void *a, const void *b) ++{ ++ return (int)(*(short *)a - *(short *)b); ++} ++ ++static void rotate_frame(Goodix511Pix frame[GOODIX511_FRAME_SIZE]) ++{ ++ Goodix511Pix buff[GOODIX511_FRAME_SIZE]; ++ ++ for (int y = 0; y != GOODIX511_HEIGHT; ++y) ++ { ++ for (int x = 0; x != GOODIX511_WIDTH; ++x) ++ { ++ buff[x * GOODIX511_WIDTH + y] = frame[x + y * GOODIX511_WIDTH]; ++ } ++ } ++ memcpy(frame, buff, GOODIX511_FRAME_SIZE); ++} ++static void squash_frame(Goodix511Pix *frame, guint8 *squashed) ++{ ++ for (int i = 0; i != GOODIX511_FRAME_SIZE; ++i) ++ { ++ squashed[i] = squash(frame[i]); ++ } ++} ++/** ++ * @brief Squashes the 12 bit pixels of a raw frame into the 4 bit pixels used ++ * by libfprint. ++ * @details Borrowed from the elan driver. We reduce frames to ++ * within the max and min. ++ * ++ * @param frame ++ * @param squashed ++ */ ++static void squash_frame_linear(Goodix511Pix *frame, guint8 *squashed) ++{ ++ Goodix511Pix min = 0xffff; ++ Goodix511Pix max = 0; ++ ++ for (int i = 0; i != GOODIX511_FRAME_SIZE; ++i) ++ { ++ const Goodix511Pix pix = frame[i]; ++ if (pix < min) ++ { ++ min = pix; ++ } ++ if (pix > max) ++ { ++ max = pix; ++ } ++ } ++ ++ for (int i = 0; i != GOODIX511_FRAME_SIZE; ++i) ++ { ++ const Goodix511Pix pix = frame[i]; ++ if (pix - min == 0 || max - min == 0) ++ { ++ squashed[i] = 0; ++ } ++ else ++ { ++ squashed[i] = (pix - min) * 0xff / (max - min); ++ } ++ } ++} ++ ++/** ++ * @brief Subtracts the background from the frame ++ * ++ * @param frame ++ * @param background ++ */ ++static gboolean postprocess_frame(Goodix511Pix frame[GOODIX511_FRAME_SIZE], ++ Goodix511Pix background[GOODIX511_FRAME_SIZE]) ++{ ++ int sum = 0; ++ for (int i = 0; i != GOODIX511_FRAME_SIZE; ++i) ++ { ++ Goodix511Pix *og_px = frame + i; ++ Goodix511Pix bg_px = background[i]; ++ ++ if (*og_px >= bg_px) ++ { ++ *og_px = 4095; ++ } ++ else ++ { ++ *og_px -= bg_px; ++ } ++ ++ sum += *og_px; ++ } ++ if (sum == 0) ++ { ++ fp_warn("frame darker than background, finger on scanner during " ++ "calibration?"); ++ } ++ ++ return sum != 0; ++} ++ ++typedef struct _frame_processing_info ++{ ++ FpiDeviceGoodixTls511 *dev; ++ GSList **frames; ++ ++} frame_processing_info; ++ ++static void process_frame(Goodix511Pix *raw_frame, frame_processing_info *info) ++{ ++ struct fpi_frame *frame = ++ g_malloc(GOODIX511_FRAME_SIZE + sizeof(struct fpi_frame)); ++ postprocess_frame(raw_frame, info->dev->empty_img); ++ squash_frame_linear(raw_frame, frame->data); ++ ++ *(info->frames) = g_slist_append(*(info->frames), frame); ++} ++ ++static void save_frame(FpiDeviceGoodixTls511 *self, guint8 *raw) ++{ ++ Goodix511Pix *frame = malloc(GOODIX511_FRAME_SIZE * sizeof(Goodix511Pix)); ++ decode_frame(frame, raw); ++ self->frames = g_slist_append(self->frames, frame); ++#ifdef GOODIX511_DUMP_FRAMES ++ char buff[2014]; ++ snprintf(buff, sizeof(buff), "cut2/f_%d.pgm", g_slist_length(self->frames)); ++ FpImage *img = fp_image_new(GOODIX511_WIDTH, GOODIX511_HEIGHT); ++ postprocess_frame(frame, self->empty_img); ++ squash_frame_linear(frame, img->data); ++ save_image_to_pgm(img, buff); ++#endif ++} ++ ++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; ++ } ++ ++ FpiDeviceGoodixTls511 *self = FPI_DEVICE_GOODIXTLS511(dev); ++ save_frame(self, data); ++ if (g_slist_length(self->frames) <= GOODIX511_CAP_FRAMES) ++ { ++ fpi_ssm_jump_to_state(ssm, SCAN_STAGE_SWITCH_TO_FDT_MODE); ++ } ++ else ++ { ++ GSList *raw_frames = g_slist_nth(self->frames, 1); ++ ++ FpImageDevice *img_dev = FP_IMAGE_DEVICE(dev); ++ struct fpi_frame_asmbl_ctx assembly_ctx; ++ assembly_ctx.frame_width = GOODIX511_WIDTH; ++ assembly_ctx.frame_height = GOODIX511_HEIGHT; ++ assembly_ctx.image_width = GOODIX511_WIDTH * 2; ++ assembly_ctx.get_pixel = get_pix; ++ ++ GSList *frames = NULL; ++ frame_processing_info pinfo = {.dev = self, .frames = &frames}; ++ ++ g_slist_foreach(raw_frames, (GFunc)process_frame, &pinfo); ++ // frames = g_slist_reverse(frames); ++ ++ fpi_do_movement_estimation(&assembly_ctx, frames); ++ FpImage *img = fpi_assemble_frames(&assembly_ctx, frames); ++ img->flags |= FPI_IMAGE_PARTIAL; ++ ++ g_slist_free_full(frames, g_free); ++ // g_slist_free_full(self->frames, g_free); ++ // self->frames = g_slist_alloc(); ++ ++ fpi_image_device_image_captured(img_dev, img); ++ fpi_image_device_report_finger_status(img_dev, FALSE); ++ ++ fpi_ssm_next_state(ssm); ++ } ++} ++ ++enum scan_empty_img_state ++{ ++ SCAN_EMPTY_NAV0, ++ SCAN_EMPTY_GET_IMG, ++ ++ SCAN_EMPTY_NUM, ++}; ++ ++static void on_scan_empty_img(FpDevice *dev, guint8 *data, guint16 length, ++ gpointer ssm, GError *error) ++{ ++ if (error) ++ { ++ fpi_ssm_mark_failed(ssm, error); ++ return; ++ } ++ FpiDeviceGoodixTls511 *self = FPI_DEVICE_GOODIXTLS511(dev); ++ decode_frame(self->empty_img, data); ++#ifdef GOODIX511_DUMP_FRAMES ++ FpImage *bgk = fp_image_new(GOODIX511_WIDTH, GOODIX511_HEIGHT); ++ squash_frame(self->empty_img, bgk->data); ++ save_image_to_pgm(bgk, "./background.pgm"); ++#endif ++ fpi_ssm_next_state(ssm); ++} ++static void scan_empty_run(FpiSsm *ssm, FpDevice *dev) ++{ ++ ++ switch (fpi_ssm_get_cur_state(ssm)) ++ { ++ case SCAN_EMPTY_NAV0: ++ goodix_send_nav_0(dev, check_none_cmd, ssm); ++ break; ++ ++ case SCAN_EMPTY_GET_IMG: ++ goodix_tls_read_image(dev, on_scan_empty_img, ssm); ++ break; ++ } ++} ++ ++static void scan_empty_img(FpDevice *dev, FpiSsm *ssm) ++{ ++ fpi_ssm_start_subsm(ssm, fpi_ssm_new(dev, scan_empty_run, SCAN_EMPTY_NUM)); ++} ++ ++static void scan_get_img(FpDevice *dev, FpiSsm *ssm) ++{ ++ goodix_tls_read_image(dev, scan_on_read_img, ssm); ++} ++ ++const guint8 fdt_switch_state_mode[] = { ++ 0x0d, ++ 0x01, ++ 0x80, ++ 0xaf, ++ 0x80, ++ 0xbf, ++ 0x80, ++ 0xa4, ++ 0x80, ++ 0xb8, ++ 0x80, ++ 0xa8, ++ 0x80, ++ 0xb7, ++}; ++ ++const guint8 fdt_switch_state_down[] = { ++ 0x0c, ++ 0x01, ++ 0x80, ++ 0xaf, ++ 0x80, ++ 0xbf, ++ 0x80, ++ 0xa4, ++ 0x80, ++ 0xb8, ++ 0x80, ++ 0xa8, ++ 0x80, ++ 0xb7, ++}; ++ ++static void scan_run_state(FpiSsm *ssm, FpDevice *dev) ++{ ++ FpImageDevice *img_dev = FP_IMAGE_DEVICE(dev); ++ ++ switch (fpi_ssm_get_cur_state(ssm)) ++ { ++ case SCAN_STAGE_CALIBRATE: ++ scan_empty_img(dev, ssm); ++ break; ++ ++ case SCAN_STAGE_SWITCH_TO_FDT_MODE: ++ goodix_send_mcu_switch_to_fdt_mode(dev, (guint8 *)fdt_switch_state_mode, ++ sizeof(fdt_switch_state_mode), NULL, ++ check_none_cmd, ssm); ++ break; ++ ++ case SCAN_STAGE_SWITCH_TO_FDT_DOWN: ++ goodix_send_mcu_switch_to_fdt_down(dev, (guint8 *)fdt_switch_state_down, ++ sizeof(fdt_switch_state_down), NULL, ++ check_none_cmd, ssm); ++ break; ++ case SCAN_STAGE_GET_IMG: ++ fpi_image_device_report_finger_status(img_dev, TRUE); ++ scan_get_img(dev, 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); ++ return; ++ } ++ fp_dbg("finished scan"); ++} ++ ++static void scan_start(FpiDeviceGoodixTls511 *dev) ++{ ++ fpi_ssm_start(fpi_ssm_new(FP_DEVICE(dev), scan_run_state, SCAN_STAGE_NUM), ++ scan_complete); ++} ++ ++// ---- SCAN SECTION END ---- ++ ++// ---- DEV SECTION START ---- ++ ++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_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_activate(FpImageDevice *img_dev) ++{ ++ FpDevice *dev = FP_DEVICE(img_dev); ++ ++ fpi_ssm_start(fpi_ssm_new(dev, activate_run_state, ACTIVATE_NUM_STATES), ++ activate_complete); ++} ++ ++static void dev_change_state(FpImageDevice *img_dev, FpiImageDeviceState state) ++{ ++ FpiDeviceGoodixTls511 *self = FPI_DEVICE_GOODIXTLS511(img_dev); ++ G_DEBUG_HERE(); ++ ++ if (state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON) ++ { ++ scan_start(self); ++ } ++} ++ ++static void goodix511_reset_state(FpiDeviceGoodixTls511 *self) {} ++ ++static void dev_deactivate(FpImageDevice *img_dev) ++{ ++ FpDevice *dev = FP_DEVICE(img_dev); ++ goodix_reset_state(dev); ++ GError *error = NULL; ++ goodix_shutdown_tls(dev, &error); ++ goodix511_reset_state(FPI_DEVICE_GOODIXTLS511(img_dev)); ++ fpi_image_device_deactivate_complete(img_dev, error); ++} ++ ++// ---- DEV SECTION END ---- ++ ++static void fpi_device_goodixtls511_init(FpiDeviceGoodixTls511 *self) ++{ ++ self->frames = g_slist_alloc(); ++} ++ ++static void fpi_device_goodixtls511_class_init( ++ FpiDeviceGoodixTls511Class *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); ++ ++ gx_class->interface = GOODIX_511_INTERFACE; ++ gx_class->ep_in = GOODIX_511_EP_IN; ++ gx_class->ep_out = GOODIX_511_EP_OUT; ++ ++ dev_class->id = "goodixtls511"; ++ dev_class->full_name = "Goodix TLS Fingerprint Sensor 511"; ++ dev_class->type = FP_DEVICE_TYPE_USB; ++ dev_class->id_table = id_table; ++ ++ dev_class->scan_type = FP_SCAN_TYPE_PRESS; ++ ++ // TODO ++ img_dev_class->bz3_threshold = 24; ++ img_dev_class->img_width = GOODIX511_WIDTH; ++ img_dev_class->img_height = GOODIX511_HEIGHT; ++ ++ img_dev_class->img_open = dev_init; ++ img_dev_class->img_close = dev_deinit; ++ img_dev_class->activate = dev_activate; ++ img_dev_class->change_state = dev_change_state; ++ img_dev_class->deactivate = dev_deactivate; ++ ++ fpi_device_class_auto_initialize_features(dev_class); ++} +diff --git a/libfprint/drivers/goodixtls/goodix511.h b/libfprint/drivers/goodixtls/goodix511.h +new file mode 100644 +index 00000000..7e00fa5b +--- /dev/null ++++ b/libfprint/drivers/goodixtls/goodix511.h +@@ -0,0 +1,64 @@ ++// Goodix Tls driver for libfprint ++ ++// Copyright (C) 2021 Alexander Meiler ++// Copyright (C) 2021 Matthieu CHARETTE ++ ++// 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_511_INTERFACE (0) ++#define GOODIX_511_EP_IN (0x1 | FPI_USB_ENDPOINT_IN) ++#define GOODIX_511_EP_OUT (0x1 | FPI_USB_ENDPOINT_OUT) ++ ++#define GOODIX_511_FIRMWARE_VERSION ("GF_ST411SEC_APP_12117") ++ ++#define GOODIX_511_PSK_FLAGS (0xbb020003) ++ ++#define GOODIX_511_RESET_NUMBER (2048) ++ ++const guint8 goodix_511_psk_0[] = { ++ 0xba, 0x1a, 0x86, 0x03, 0x7c, 0x1d, 0x3c, 0x71, 0xc3, 0xaf, 0x34, ++ 0x49, 0x55, 0xbd, 0x69, 0xa9, 0xa9, 0x86, 0x1d, 0x9e, 0x91, 0x1f, ++ 0xa2, 0x49, 0x85, 0xb6, 0x77, 0xe8, 0xdb, 0xd7, 0x2d, 0x43}; ++ ++guint8 goodix_511_config[] = { ++ 0x70, 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, 0x04, 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, 0x1f, 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, 0x89, 0x2e}; ++ ++static const FpIdEntry id_table[] = { ++ {.vid = 0x27c6, .pid = 0x5110}, ++ {.vid = 0, .pid = 0, .driver_data = 0}, ++}; +diff --git a/libfprint/drivers/goodixtls/goodix53xd.c b/libfprint/drivers/goodixtls/goodix53xd.c +new file mode 100644 +index 00000000..6bf8d5f1 +--- /dev/null ++++ b/libfprint/drivers/goodixtls/goodix53xd.c +@@ -0,0 +1,860 @@ ++// Goodix Tls driver for libfprint ++ ++// Copyright (C) 2021 Alexander Meiler ++// Copyright (C) 2021 Matthieu CHARETTE ++// Copyright (C) 2021 Michael Teuscher ++ ++// 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-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 ++#define FP_COMPONENT "goodixtls53xd" ++ ++#include ++#include ++ ++#include "drivers_api.h" ++#include "goodix.h" ++#include "goodix_proto.h" ++#include "goodix53xd.h" ++ ++#include ++ ++#define GOODIX53XD_WIDTH 64 ++#define GOODIX53XD_HEIGHT 80 ++#define GOODIX53XD_SCAN_WIDTH 64 ++#define GOODIX53XD_FRAME_SIZE (GOODIX53XD_WIDTH * GOODIX53XD_HEIGHT) ++// For every 4 pixels there are 6 bytes and there are 8 extra start bytes and 5 ++// extra end ++#define GOODIX53XD_RAW_FRAME_SIZE \ ++ (GOODIX53XD_HEIGHT * GOODIX53XD_SCAN_WIDTH) / 4 * 6 ++#define GOODIX53XD_CAP_FRAMES 1 // Number of frames we capture per swipe ++ ++typedef unsigned short Goodix53xdPix; ++ ++struct _FpiDeviceGoodixTls53XD { ++ FpiDeviceGoodixTls parent; ++ ++ guint8* otp; ++ ++ GSList* frames; ++ ++ Goodix53xdPix empty_img[GOODIX53XD_FRAME_SIZE]; ++}; ++ ++G_DECLARE_FINAL_TYPE(FpiDeviceGoodixTls53XD, fpi_device_goodixtls53xd, FPI, ++ DEVICE_GOODIXTLS53XD, FpiDeviceGoodixTls); ++ ++G_DEFINE_TYPE(FpiDeviceGoodixTls53XD, fpi_device_goodixtls53xd, ++ FPI_TYPE_DEVICE_GOODIXTLS); ++ ++typedef struct ++_frame_processing_info { ++ FpiDeviceGoodixTls53XD* dev; ++ GSList** frames; ++ ++} frame_processing_info; ++// ---- ACTIVE SECTION START ---- ++ ++/** ++ * @brief Checks nothing and moves the state machine to the next state ++ * ++ * @param dev ++ * @param user_data ++ * @param error ++ */ ++static void ++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); ++} ++/** ++ * @brief Checks that firmware name is expected and advances to next state ++ * ++ * @param dev ++ * @param firmware ++ * @param user_data ++ * @param error ++ */ ++static void ++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); ++ ++ if (strcmp(firmware, GOODIX_53XD_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); ++} ++/** ++ * @brief Checks that device was reset properly and advances to next state ++ * ++ * @param dev ++ * @param success ++ * @param number ++ * @param user_data ++ * @param error ++ */ ++static void ++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); ++ ++ if (number != GOODIX_53XD_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); ++} ++/** ++ * @brief Check if preshared key is as expected then advances to next state ++ * ++ * @param dev ++ * @param success ++ * @param flags ++ * @param psk ++ * @param length ++ * @param user_data ++ * @param error ++ */ ++static void ++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); ++ ++ if (flags != GOODIX_53XD_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 != sizeof(goodix_53xd_psk_0)) { ++ 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, goodix_53xd_psk_0, sizeof(goodix_53xd_psk_0))) { ++ 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); ++} ++/** ++ * @brief Checks for error and advances to next state ++ * ++ * @param dev ++ * @param user_data ++ * @param err ++ */ ++static void ++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); ++} ++ ++static void ++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); ++ } ++} ++ ++static void ++read_otp_callback(FpDevice* dev, guint8* data, guint16 len, ++ gpointer ssm, GError* err) ++{ ++ if (err) { ++ fpi_ssm_mark_failed(ssm, err); ++ return; ++ } ++ if (len < 64) { ++ fpi_ssm_mark_failed(ssm, g_error_new(FP_DEVICE_ERROR, ++ FP_DEVICE_ERROR_DATA_INVALID, ++ "OTP is invalid (len: %d)", 64)); ++ return; ++ } ++ FpiDeviceGoodixTls53XD* self = FPI_DEVICE_GOODIXTLS53XD(dev); ++ self->otp = malloc(64); ++ memcpy(self->otp, data, len); ++ fpi_ssm_next_state(ssm); ++} ++ ++/** ++ * @brief Runs functions depending on current state of SSM ++ * @details This is the main part of the driver. This function runs every time ++ * the state machine moves to the next state. This function looks at the ++ * current state of the ssm responds accordingly ++ * ++ * @param ssm ++ * @param dev ++ */ ++static void ++activate_run_state(FpiSsm* ssm, FpDevice* dev) ++{ ++ ++ switch (fpi_ssm_get_cur_state(ssm)) { ++ case ACTIVATE_READ_AND_NOP: ++ // Nop seems to clear the previous command buffer. But we are ++ // unable to do so. ++ goodix_start_read_loop(dev); ++ goodix_send_nop(dev, check_none, ssm); ++ break; ++ ++ case ACTIVATE_ENABLE_CHIP: ++ goodix_send_enable_chip(dev, TRUE, check_none, ssm); ++ break; ++ ++ case ACTIVATE_NOP: ++ goodix_send_nop(dev, check_none, ssm); ++ break; ++ ++ case ACTIVATE_CHECK_FW_VER: ++ goodix_send_firmware_version(dev, check_firmware_version, ssm); ++ break; ++ ++ case ACTIVATE_CHECK_PSK: ++ goodix_send_preset_psk_read(dev, GOODIX_53XD_PSK_FLAGS, 32, ++ check_preset_psk_read, ssm); ++ break; ++ ++ case ACTIVATE_RESET: ++ goodix_send_reset(dev, TRUE, 20, check_reset, ssm); ++ break; ++ ++ case ACTIVATE_OTP: ++ goodix_send_read_otp(dev, read_otp_callback, ssm); ++ break; ++ ++ case ACTIVATE_SET_MCU_IDLE: ++ goodix_send_mcu_switch_to_idle_mode(dev, 20, check_idle, ssm); ++ break; ++ ++ case ACTIVATE_SET_MCU_CONFIG: ++ goodix_send_upload_config_mcu(dev, goodix_53xd_config, ++ sizeof(goodix_53xd_config), NULL, ++ check_config_upload, ssm); ++ break; ++ default: // TODO What happens if we have a bad state? ++ } ++} ++/** ++ * @brief Checks for error, then marks device activation as complete if no error ++ * @details Called when finishing device activation, either successful or not ++ * ++ * @param dev ++ * @param user_data ++ * @param error ++ */ ++static void ++tls_activation_complete(FpDevice* dev, gpointer user_data, ++ GError* error) ++{ ++ if (error) { ++ fp_err("failed to complete tls activation: %s", error->message); ++ return; ++ } ++ FpImageDevice* image_dev = FP_IMAGE_DEVICE(dev); ++ ++ fpi_image_device_activate_complete(image_dev, error); ++} ++/** ++ * @brief Activates TLS if no errors have occured, otherwise spit an error ++ * ++ * @param ssm ++ * @param dev ++ * @param error ++ */ ++static void ++activate_complete(FpiSsm* ssm, FpDevice* dev, GError* error) ++{ ++ G_DEBUG_HERE(); ++ if (!error) ++ goodix_tls(dev, tls_activation_complete, NULL); ++ else { ++ fp_err("failed during activation: %s (code: %d)", error->message, ++ error->code); ++ fpi_image_device_activate_complete(FP_IMAGE_DEVICE(dev), error); ++ } ++} ++ ++// ---- ACTIVE SECTION END ---- ++ ++// ----------------------------------------------------------------------------- ++ ++// ---- SCAN SECTION START ---- ++/** ++ * @brief Checks for errors and moves to next state in ssm ++ * ++ * @param dev ++ * @param data ++ * @param len ++ * @param ssm ++ * @param err ++ */ ++static void ++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); ++} ++/** ++ * @brief Get the pixel located at (x,y) ++ * ++ * @param ctx ++ * @param frame ++ * @param x ++ * @param y ++ * @return unsigned char ++ */ ++static unsigned char ++get_pix(struct fpi_frame_asmbl_ctx* ctx, ++ struct fpi_frame* frame, unsigned int x, ++ unsigned int y) ++{ ++ return frame->data[x + y * GOODIX53XD_WIDTH]; ++} ++/** ++ * @brief Transforms raw image data from the sensor to usable image data ++ * ++ * @param frame ++ * @param raw_frame ++ */ ++static void ++decode_frame(Goodix53xdPix frame[GOODIX53XD_FRAME_SIZE], ++ const guint8* raw_frame) ++{ ++ Goodix53xdPix uncropped[GOODIX53XD_SCAN_WIDTH * GOODIX53XD_HEIGHT]; ++ Goodix53xdPix* pix = uncropped; ++ for (int i = 0; i < GOODIX53XD_RAW_FRAME_SIZE; 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); ++ } ++ ++ for (int y = 0; y != GOODIX53XD_HEIGHT; ++y) { ++ for (int x = 0; x != GOODIX53XD_WIDTH; ++x) { ++ const int idx = x + y * GOODIX53XD_SCAN_WIDTH; ++ frame[x + y * GOODIX53XD_WIDTH] = uncropped[idx]; ++ } ++ } ++} ++ ++/** ++ * @brief Squashes the 12 bit pixels of a raw frame into the 4 bit pixels used ++ * by libfprint. ++ * @details Borrowed from the elan driver. We reduce frames to ++ * within the max and min. ++ * ++ * @param frame ++ * @param squashed ++ */ ++static void ++squash_frame_linear(Goodix53xdPix* frame, guint8* squashed) ++{ ++ Goodix53xdPix min = 0xffff; ++ Goodix53xdPix max = 0; ++ ++ for (int i = 0; i != GOODIX53XD_FRAME_SIZE; ++i) { ++ const Goodix53xdPix pix = frame[i]; ++ if (pix < min) { ++ min = pix; ++ } ++ if (pix > max) { ++ max = pix; ++ } ++ } ++ ++ for (int i = 0; i != GOODIX53XD_FRAME_SIZE; ++i) { ++ const Goodix53xdPix pix = frame[i]; ++ if (pix - min == 0 || max - min == 0) { ++ squashed[i] = 0; ++ } ++ else { ++ squashed[i] = (pix - min) * 0xff / (max - min); ++ } ++ } ++} ++ ++/** ++ * @brief Subtracts the background from the frame ++ * ++ * @param frame ++ * @param background ++ */ ++ ++static void ++process_frame(Goodix53xdPix* raw_frame, frame_processing_info* info) ++{ ++ struct fpi_frame* frame = ++ g_malloc(GOODIX53XD_FRAME_SIZE + sizeof(struct fpi_frame)); ++ //postprocess_frame(raw_frame, info->dev->empty_img); ++ squash_frame_linear(raw_frame, frame->data); ++ ++ *(info->frames) = g_slist_append(*(info->frames), frame); ++} ++/** ++ * @brief Save the frame to an internal buffer ++ * ++ * @param self ++ * @param raw ++ */ ++static void ++save_frame(FpiDeviceGoodixTls53XD* self, guint8* raw) ++{ ++ Goodix53xdPix* frame = ++ malloc(GOODIX53XD_FRAME_SIZE * sizeof(Goodix53xdPix)); ++ decode_frame(frame, raw); ++ self->frames = g_slist_append(self->frames, frame); ++} ++/** ++ * @brief ++ * ++ * @param dev ++ * @param data ++ * @param len ++ * @param ssm ++ * @param err ++ */ ++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; ++ } ++ ++ ++ FpiDeviceGoodixTls53XD* self = FPI_DEVICE_GOODIXTLS53XD(dev); ++ save_frame(self, data); ++ if (g_slist_length(self->frames) <= GOODIX53XD_CAP_FRAMES) { ++ fpi_ssm_jump_to_state(ssm, SCAN_STAGE_SWITCH_TO_FDT_MODE); ++ } ++ else { ++ GSList* raw_frames = g_slist_nth(self->frames, 1); ++ ++ FpImageDevice* img_dev = FP_IMAGE_DEVICE(dev); ++ struct fpi_frame_asmbl_ctx assembly_ctx; ++ assembly_ctx.frame_width = GOODIX53XD_WIDTH; ++ assembly_ctx.frame_height = GOODIX53XD_HEIGHT; ++ assembly_ctx.image_width = GOODIX53XD_WIDTH*3; ++ assembly_ctx.get_pixel = get_pix; ++ ++ GSList* frames = NULL; ++ frame_processing_info pinfo = {.dev = self, .frames = &frames}; ++ ++ g_slist_foreach(raw_frames, (GFunc) process_frame, &pinfo); ++ frames = g_slist_reverse(frames); ++ ++ fpi_do_movement_estimation(&assembly_ctx, frames); ++ FpImage* img = fpi_assemble_frames(&assembly_ctx, frames); ++ ++ g_slist_free_full(frames, g_free); ++ g_slist_free_full(self->frames, g_free); ++ self->frames = g_slist_alloc(); ++ ++ fpi_image_device_image_captured(img_dev, img); ++ ++ ++ fpi_image_device_report_finger_status(img_dev, FALSE); ++ ++ fpi_ssm_next_state(ssm); ++ } ++} ++ ++ ++static void ++goodix_53xd_send_mcu_get_image(FpDevice* dev, guint8* payload, guint16 length, ++ GoodixImageCallback 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_MCU_GET_IMAGE, payload, ++ length, NULL, TRUE, GOODIX_TIMEOUT, TRUE, ++ goodix_receive_default, cb_info); ++ return; ++ } ++ ++ goodix_send_protocol(dev, GOODIX_CMD_MCU_GET_IMAGE, payload, ++ length, NULL, TRUE, GOODIX_TIMEOUT, TRUE, ++ NULL, NULL); ++} ++ ++static void ++goodix_53xd_tls_read_image(FpDevice* dev, guint8* payload, ++ guint16 length, ++ 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_53xd_send_mcu_get_image(dev, payload, length, ++ goodix_tls_ready_image_handler, cb_info); ++} ++ ++static void ++scan_get_img(FpDevice* dev, FpiSsm* ssm) ++{ ++ FpImageDevice* img_dev = FP_IMAGE_DEVICE(dev); ++ FpiDeviceGoodixTls53XD* self = FPI_DEVICE_GOODIXTLS53XD(img_dev); ++ guint8 payload[] = {0x41, 0x03, self->otp[26], 0x00, ++ self->otp[26] - 6, 0x00, self->otp[45], 0x00, self->otp[45] - 4, 0x00}; ++ goodix_53xd_tls_read_image(dev, (guint8 *)&payload, ++ sizeof(payload), scan_on_read_img, ssm); ++} ++ ++static void ++goodix_send_mcu_switch_to_fdt_down_with_no_reply(FpDevice *dev, ++ guint8 *mode, ++ guint16 length, ++ GDestroyNotify free_func, ++ GoodixDefaultCallback 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_MCU_SWITCH_TO_FDT_DOWN, mode, length, ++ free_func, TRUE, 0, FALSE, goodix_receive_default, ++ cb_info); ++ return; ++ } ++ ++ goodix_send_protocol(dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_DOWN, mode, length, ++ free_func, TRUE, 0, FALSE, NULL, NULL); ++} ++ ++static void ++goodix_send_mcu_switch_to_fdt_mode_no_reply(FpDevice *dev, ++ guint8 *mode, ++ guint16 length, ++ GDestroyNotify free_func, ++ GoodixDefaultCallback 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_MCU_SWITCH_TO_FDT_MODE, mode, length, ++ free_func, TRUE, 0, FALSE, goodix_receive_default, ++ cb_info); ++ return; ++ } ++ ++ goodix_send_protocol(dev, GOODIX_CMD_MCU_SWITCH_TO_FDT_MODE, mode, length, ++ free_func, TRUE, 0, FALSE, NULL, NULL); ++} ++ ++static void ++scan_run_state(FpiSsm* ssm, FpDevice* dev) ++{ ++ FpImageDevice* img_dev = FP_IMAGE_DEVICE(dev); ++ FpiDeviceGoodixTls53XD* self = FPI_DEVICE_GOODIXTLS53XD(img_dev); ++ ++ ++ switch (fpi_ssm_get_cur_state(ssm)) { ++ ++ case SCAN_STAGE_SWITCH_TO_FDT_MODE: ++ goodix_send_mcu_switch_to_fdt_mode_no_reply(dev, ++ (guint8 *)fdt_switch_state_mode_53xd, ++ sizeof(fdt_switch_state_mode_53xd), NULL, ++ check_none_cmd, ssm); ++ break; ++ ++ case SCAN_STAGE_SWITCH_TO_FDT_DOWN: ++ // FDT Down Cali ++ fdt_switch_state_down_53xd[2] = self->otp[33]; ++ fdt_switch_state_down_53xd[4] = self->otp[41]; ++ fdt_switch_state_down_53xd[6] = self->otp[42]; ++ fdt_switch_state_down_53xd[8] = self->otp[43]; ++ ++ // First FDT down must not send a reply ++ fdt_switch_state_down_53xd[26] = 0x00; ++ goodix_send_mcu_switch_to_fdt_down_with_no_reply(dev, ++ (guint8*) fdt_switch_state_down_53xd, ++ sizeof(fdt_switch_state_down_53xd), ++ NULL, receive_fdt_down_ack, ssm); ++ break; ++ case SCAN_STAGE_GET_IMG: ++ fpi_image_device_report_finger_status(img_dev, TRUE); ++ guint16 payload = {0x05}; ++ goodix_send_write_sensor_register(dev, ++ 556, payload, write_sensor_complete, ssm); ++ break; ++ } ++} ++ ++static void ++receive_fdt_down_ack(FpDevice* dev, guint8* data, guint16 len, ++ gpointer ssm, GError* err) ++{ ++ if (err) { ++ fpi_ssm_mark_failed(ssm, err); ++ return; ++ } ++ ++ // Second FDT down must send a response ++ fdt_switch_state_down_53xd[26] = 0x01; ++ goodix_send_mcu_switch_to_fdt_down(dev, ++ (guint8*) fdt_switch_state_down_53xd, ++ sizeof(fdt_switch_state_down_53xd), NULL, ++ check_none_cmd, ssm); ++} ++ ++static void ++write_sensor_complete(FpDevice *dev, ++ gpointer user_data, GError *error) ++{ ++ if (error) { ++ fp_err("failed to scan: %s (code: %d)", error->message, error->code); ++ return; ++ } ++ scan_get_img(dev, user_data); ++} ++ ++static void ++scan_complete(FpiSsm* ssm, FpDevice* dev, GError* error) ++{ ++ if (error) { ++ fp_err("failed to scan: %s (code: %d)", error->message, error->code); ++ return; ++ } ++ fp_dbg("finished scan"); ++} ++ ++static void ++scan_start(FpiDeviceGoodixTls53XD* dev) ++{ ++ fpi_ssm_start(fpi_ssm_new(FP_DEVICE(dev), scan_run_state, SCAN_STAGE_NUM), ++ scan_complete); ++} ++ ++// ---- SCAN SECTION END ---- ++ ++// ----------------------------------------------------------------------------- ++ ++// ---- DEV SECTION START ---- ++ ++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_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_activate(FpImageDevice *img_dev) { ++ FpDevice* dev = FP_DEVICE(img_dev); ++ FpiSsm* ssm = fpi_ssm_new(dev, activate_run_state, ACTIVATE_NUM_STATES); ++ fpi_ssm_start(ssm, activate_complete); ++} ++ ++static void ++dev_change_state(FpImageDevice* img_dev, FpiImageDeviceState state) ++{ ++ FpiDeviceGoodixTls53XD* self = FPI_DEVICE_GOODIXTLS53XD(img_dev); ++ G_DEBUG_HERE(); ++ ++ if (state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON) { ++ scan_start(self); ++ } ++} ++ ++static void ++goodix53xd_reset_state(FpiDeviceGoodixTls53XD* self) {} ++ ++static void ++dev_deactivate(FpImageDevice *img_dev) { ++ FpDevice* dev = FP_DEVICE(img_dev); ++ goodix_reset_state(dev); ++ GError* error = NULL; ++ goodix_shutdown_tls(dev, &error); ++ goodix53xd_reset_state(FPI_DEVICE_GOODIXTLS53XD(img_dev)); ++ fpi_image_device_deactivate_complete(img_dev, error); ++} ++ ++// ---- DEV SECTION END ---- ++ ++static void ++fpi_device_goodixtls53xd_init(FpiDeviceGoodixTls53XD* self) ++{ ++ self->frames = g_slist_alloc(); ++} ++ ++/* ++ This is the only thing the rest of libfprint cares about. This tells ++ libfprint how to interact with the device, that it is a image based device, ++ usb based device, which endpoints to use for it, etc ++*/ ++static void ++fpi_device_goodixtls53xd_class_init(FpiDeviceGoodixTls53XDClass *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); ++ ++ gx_class->interface = GOODIX_53XD_INTERFACE; ++ gx_class->ep_in = GOODIX_53XD_EP_IN; ++ gx_class->ep_out = GOODIX_53XD_EP_OUT; ++ ++ dev_class->id = "goodixtls53xd"; ++ dev_class->full_name = "Goodix TLS Fingerprint Sensor 53XD"; ++ dev_class->type = FP_DEVICE_TYPE_USB; ++ dev_class->id_table = id_table; // Devices supported by driver ++ ++ dev_class->scan_type = FP_SCAN_TYPE_PRESS; // Either scan or swipe ++ ++ img_dev_class->bz3_threshold = 24; // Detection threshold ++ img_dev_class->img_width = GOODIX53XD_WIDTH; // Only given for constant width ++ img_dev_class->img_height = GOODIX53XD_HEIGHT; // Same but height ++ ++ img_dev_class->img_open = dev_init; // Called to claim and open device ++ img_dev_class->img_close = dev_deinit; // Called to close and ++ // release device ++ img_dev_class->activate = dev_activate; // Called to start finger scanning ++ // and/or finger detection ++ img_dev_class->deactivate = dev_deactivate; // Called to stop waiting ++ //for finger ++ img_dev_class->change_state = dev_change_state; // Called anytime the device ++ // changes state, such as ++ // going from idle to ++ // waiting for finger ++ ++ ++ fpi_device_class_auto_initialize_features(dev_class); ++} +diff --git a/libfprint/drivers/goodixtls/goodix53xd.h b/libfprint/drivers/goodixtls/goodix53xd.h +new file mode 100644 +index 00000000..171e4fdd +--- /dev/null ++++ b/libfprint/drivers/goodixtls/goodix53xd.h +@@ -0,0 +1,209 @@ ++// Goodix Tls driver for libfprint ++ ++// Copyright (C) 2021 Alexander Meiler ++// Copyright (C) 2021 Matthieu CHARETTE ++// Copyright (C) 2021 Michael Teuscher ++ ++// 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_53XD_INTERFACE (0) ++#define GOODIX_53XD_EP_IN (0x83 | FPI_USB_ENDPOINT_IN) ++#define GOODIX_53XD_EP_OUT (0x1 | FPI_USB_ENDPOINT_OUT) ++ ++#define GOODIX_53XD_FIRMWARE_VERSION ("GF5298_GM168SEC_APP_13016") ++ ++#define GOODIX_53XD_PSK_FLAGS (0xbb020001) ++ ++#define GOODIX_53XD_RESET_NUMBER (2048) ++ ++/* ++ The preshared key that is used for TLS communication. This should NOT be ++ hardcoded, we need to address this before shipping the driver ++ TODO: Do not let this go into prod for fuck's sake ++ ++ Preshared key is 32 bytes in length ++*/ ++const guint8 ++goodix_53xd_psk_0[] = { ++ 0x66, 0x68, 0x7a, 0xad, 0xf8, 0x62, 0xbd, 0x77, ++ 0x6c, 0x8f, 0xc1, 0x8b, 0x8e, 0x9f, 0x8e, 0x20, ++ 0x08, 0x97, 0x14, 0x85, 0x6e, 0xe2, 0x33, 0xb3, ++ 0x90, 0x2a, 0x59, 0x1d, 0x0d, 0x5f, 0x29, 0x25 ++}; ++ ++/* ++ This is a binary blob that the proprietary driver calls the config. We have ++ no idea what it means, but here it is! ++ ++ 256 bytes in length ++*/ ++guint8 ++goodix_53xd_config[] = { ++ // 0-31 ++ 0x70, 0x11, 0x60, 0x71, 0x2c, 0x9d, 0x2c, 0xc9, ++ 0x1c, 0xe5, 0x18, 0xfd, 0x00, 0xfd, 0x00, 0xfd, ++ 0x03, 0xba, 0x00, 0x01, 0x80, 0xca, 0x00, 0x08, ++ 0x00, 0x84, 0x00, 0xbe, 0xc3, 0x86, 0x00, 0xb1, ++ // 32-63 ++ 0xb6, 0x88, 0x00, 0xba, 0xba, 0x8a, 0x00, 0xb3, ++ 0xb3, 0x8c, 0x00, 0xbc, 0xbc, 0x8e, 0x00, 0xb1, ++ 0xb1, 0x90, 0x00, 0xbb, 0xbb, 0x92, 0x00, 0xb1, ++ 0xb1, 0x94, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, ++ // 64-95 ++ 0x00, 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, ++ // 96-127 ++ 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, ++ // 128-159 ++ 0x00, 0x5c, 0x00, 0x01, 0x01, 0x56, 0x00, 0x24, ++ 0x20, 0x58, 0x00, 0x01, 0x02, 0x32, 0x00, 0x04, ++ 0x02, 0x66, 0x00, 0x00, 0x02, 0x7c, 0x00, 0x00, ++ 0x58, 0x82, 0x00, 0x7f, 0x08, 0x2a, 0x01, 0x82, ++ // 160-191 ++ 0x07, 0x22, 0x00, 0x01, 0x20, 0x24, 0x00, 0x14, ++ 0x00, 0x80, 0x00, 0x01, 0x40, 0x5c, 0x00, 0xe7, ++ 0x00, 0x56, 0x00, 0x06, 0x14, 0x58, 0x00, 0x04, ++ 0x02, 0x32, 0x00, 0x0c, 0x02, 0x66, 0x00, 0x00, ++ // 192-223 ++ 0x02, 0x7c, 0x00, 0x00, 0x58, 0x82, 0x00, 0x80, ++ 0x08, 0x2a, 0x01, 0x08, 0x00, 0x5c, 0x00, 0x01, ++ 0x01, 0x54, 0x00, 0x00, 0x01, 0x62, 0x00, 0x08, ++ 0x04, 0x64, 0x00, 0x10, 0x00, 0x66, 0x00, 0x00, ++ // 224-255 ++ 0x02, 0x7c, 0x00, 0x00, 0x58, 0x2a, 0x01, 0x08, ++ 0x00, 0x5c, 0x00, 0xdc, 0x00, 0x52, 0x00, 0x08, ++ 0x00, 0x54, 0x00, 0x00, 0x01, 0x66, 0x00, 0x00, ++ 0x02, 0x7c, 0x00, 0x00, 0x58, 0x20, 0xc5, 0x1d ++}; ++ ++/* ++ Another binary blob from the proprietary windows driver, switches to ++ "fdt mode". No idea what fdt stands for. Could stand for "Finger Down Touch" ++ or "Flattened Device Tree"? ++ ++ 21 bytes in length (weird size but ok) ++*/ ++ ++guint8 ++fdt_switch_state_mode_53xd[] = { ++ 0x0d, 0x01, 0x28, 0x01, 0x22, 0x01, 0x28, 0x01, ++ 0x24, 0x01, 0x91, 0x91, 0x8b, 0x8b, 0x96, 0x96, ++ 0x91, 0x91, 0x98, 0x98, 0x90, 0x90, 0x92, 0x92, ++ 0x88, 0x88, 0x00 ++}; ++ ++/* ++ Another binary blob from the proprietary windows driver, switches to ++ "fdt down" or deactivates the fdt? No idea what fdt stands for. ++ Could stand for "Finger Down Touch" or "Flattened Device Tree"? ++ ++ 21 bytes in length (weird size but ok) ++*/ ++ ++guint8 ++fdt_switch_state_down_53xd[] = { ++ 0x8c, 0x01, 0x28, 0x01, 0x22, 0x01, 0x28, 0x01, ++ 0x24, 0x01, 0x91, 0x91, 0x8b, 0x8b, 0x96, 0x96, ++ 0x91, 0x91, 0x98, 0x98, 0x90, 0x90, 0x92, 0x92, ++ 0x88, 0x88, 0x00 ++}; ++ ++/* ++ States to go through for scanning an empty image, processed by the ssm ++ TODO: Is this necessary? ++*/ ++enum ++scan_empty_img_state { ++ SCAN_EMPTY_NAV0, ++ SCAN_EMPTY_GET_IMG, ++ ++ SCAN_EMPTY_NUM, ++}; ++ ++/* ++ These are the states the driver iterates through when a device it supports ++ is "activated", with the final state just being a marker of how many states ++ there are in total ++ ++ TODO: Checking the PSK and firmware can probably be moved to device init ++*/ ++enum ++activate_states { ++ ACTIVATE_READ_AND_NOP, // First, send a no-operation ++ // command to get a clean slate ++ ACTIVATE_ENABLE_CHIP, // Second, tell the sensor to start ++ // listening for a finger ++ ACTIVATE_NOP, // Third, do nothing...? TODO ++ ACTIVATE_CHECK_FW_VER, // Fourth, check the running firmware ++ // is what we expect ++ ACTIVATE_CHECK_PSK, // Fifth, check that the preshared key ++ // used for TLS communication is as expected ++ ACTIVATE_RESET, // Sixth, no idea TODO ++ ACTIVATE_OTP, // Seventh, establish one time ++ // password for TLS communication ++ ACTIVATE_SET_MCU_IDLE, // Eighth, tell the sensor to idle ++ ACTIVATE_SET_MCU_CONFIG, // Ninth, pass configuration sensor ++ ACTIVATE_NUM_STATES, // Number of states in state machine, currently nine ++}; ++ ++/* ++ Stages for establishing one-time password for TLS communication ++*/ ++enum ++otp_write_states { ++ OTP_WRITE_1, ++ OTP_WRITE_2, ++ ++ OTP_WRITE_NUM, ++}; ++ ++/* ++ Stages to go through to acquire an image from the device, based on ++ windows proprietary driver ++*/ ++enum ++SCAN_STAGES { ++ SCAN_STAGE_SWITCH_TO_FDT_DOWN, // Set FDT (no idea what it is) to down ++ SCAN_STAGE_SWITCH_TO_FDT_MODE, // Then immediately set it to "mode"? ++ SCAN_STAGE_GET_IMG, // Actually get the image ++ ++ SCAN_STAGE_NUM ++}; ++ ++ ++ ++static void ++write_sensor_complete(FpDevice *dev, gpointer user_data, GError *error); ++static void ++receive_fdt_down_ack(FpDevice* dev, guint8* data, guint16 len, gpointer ssm, ++ GError* err); ++ ++/* ++ These are the devices that are supported by this driver, identified ++ by the vendor id (.vid) and product id (.pid) ++*/ ++static const ++FpIdEntry id_table[] = { ++ {.vid = 0x27c6, .pid = 0x538d}, ++ {.vid = 0x27c6, .pid = 0x532d}, // XPS 13 2 in 1 (7390) ++ {.vid = 0, .pid = 0, .driver_data = 0}, ++}; +\ 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 00000000..d1a2c182 +--- /dev/null ++++ b/libfprint/drivers/goodixtls/goodix_proto.c +@@ -0,0 +1,133 @@ ++// Goodix Tls driver for libfprint ++ ++// Copyright (C) 2021 Alexander Meiler ++// Copyright (C) 2021 Matthieu CHARETTE ++ ++// 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, 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 00000000..56cc2946 +--- /dev/null ++++ b/libfprint/drivers/goodixtls/goodix_proto.h +@@ -0,0 +1,161 @@ ++// Goodix Tls driver for libfprint ++ ++// Copyright (C) 2021 Alexander Meiler ++// Copyright (C) 2021 Matthieu CHARETTE ++ ++// 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_QUERY_MCU_STATE (0xae) ++#define GOODIX_CMD_ACK (0xb0) ++#define GOODIX_CMD_REQUEST_TLS_CONNECTION (0xd0) ++#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 length; ++ guint32 offset; ++ guint32 flags; ++} GoodixPresetPsk; ++ ++typedef struct __attribute__((__packed__)) _GoodixPresetPskResponse { ++ guint32 flags; ++ guint32 length; ++} GoodixPresetPskResponse; ++ ++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, 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 00000000..c4288626 +--- /dev/null ++++ b/libfprint/drivers/goodixtls/goodixtls.c +@@ -0,0 +1,185 @@ ++// Goodix Tls driver for libfprint ++ ++// Copyright (C) 2021 Alexander Meiler ++// Copyright (C) 2021 Matthieu CHARETTE ++ ++// 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 "drivers_api.h" ++#include "fp-device.h" ++#include "fpi-device.h" ++#include "glibconfig.h" ++#include "goodix.h" ++#include "goodixtls.h" ++ ++static GError* err_from_ssl(void) ++{ ++ GError* err = malloc(sizeof(GError)); ++ unsigned long code = ERR_get_error(); ++ err->code = code; ++ const char* msg = ERR_reason_error_string(code); ++ err->message = malloc(strlen(msg)); ++ strcpy(err->message, msg); ++ return err; ++} ++ ++static unsigned int tls_server_psk_server_callback(SSL *ssl, ++ const char *identity, ++ unsigned char *psk, ++ unsigned int max_psk_len) { ++ if (sizeof(goodix_511_psk_0) > max_psk_len) { ++ fp_dbg("Provided PSK R is too long for OpenSSL"); ++ return 0; ++ } ++ fp_dbg("PSK WANTED %d", max_psk_len); ++ // I don't know why we must use OPENSSL_hexstr2buf but just copying zeros ++ // doesn't work ++ const char* buff = "000000000000000000000000000000000000000000000000000000000" ++ "0000000"; ++ long len = 0; ++ unsigned char* key = OPENSSL_hexstr2buf(buff, &len); ++ memcpy(psk, key, len); ++ OPENSSL_free(key); ++ ++ 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) ++{ ++ SSL_CTX_set_ecdh_auto(ctx, 1); ++ SSL_CTX_set_dh_auto(ctx, 1); ++ SSL_CTX_set_cipher_list(ctx, "ALL"); ++ 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_send(GoodixTlsServer* self, guint8* data, guint16 length) ++{ ++ return write(self->client_fd, data, length * sizeof(guint8)); ++} ++int goodix_tls_client_recv(GoodixTlsServer* self, guint8* data, guint16 length) { ++ return read(self->client_fd, data, length * sizeof(guint8)); ++} ++ ++int goodix_tls_server_receive(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, "ALL"); ++} ++ ++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) { ++ self->connection_callback(self, err_from_ssl(), self->user_data); ++ } ++ else { ++ self->connection_callback(self, NULL, self->user_data); ++ } ++ return NULL; ++} ++ ++gboolean goodix_tls_server_deinit(GoodixTlsServer* self, GError** error) ++{ ++ SSL_shutdown(self->ssl_layer); ++ SSL_free(self->ssl_layer); ++ ++ close(self->client_fd); ++ close(self->sock_fd); ++ ++ SSL_CTX_free(self->ssl_ctx); ++ ++ return TRUE; ++} ++ ++gboolean goodix_tls_server_init(GoodixTlsServer* self, GError** error) ++{ ++ g_assert(self->connection_callback); ++ SSL_load_error_strings(); ++ OpenSSL_add_ssl_algorithms(); ++ SSL_library_init(); ++ self->ssl_ctx = tls_server_create_ctx(); ++ tls_server_config_ctx(self->ssl_ctx); ++ ++ int socks[2] = {0, 0}; ++ 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)); ++ return FALSE; ++ } ++ self->sock_fd = socks[0]; ++ self->client_fd = socks[1]; ++ ++ 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; ++ } ++ self->ssl_layer = SSL_new(self->ssl_ctx); ++ 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 00000000..66c61f45 +--- /dev/null ++++ b/libfprint/drivers/goodixtls/goodixtls.h +@@ -0,0 +1,91 @@ ++// Goodix Tls driver for libfprint ++ ++// Copyright (C) 2021 Alexander Meiler ++// Copyright (C) 2021 Matthieu CHARETTE ++ ++// 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 ++ ++#include ++ ++#define GOODIX_TLS_SERVER_PORT 4433 ++ ++// static const guint8 goodix_511_psk_0[64] = {0}; ++static const guint8 goodix_511_psk_0[] = { ++ 0xba, 0x1a, 0x86, 0x03, 0x7c, 0x1d, 0x3c, 0x71, 0xc3, 0xaf, 0x34, ++ 0x49, 0x55, 0xbd, 0x69, 0xa9, 0xa9, 0x86, 0x1d, 0x9e, 0x91, 0x1f, ++ 0xa2, 0x49, 0x85, 0xb6, 0x77, 0xe8, 0xdb, 0xd7, 0x2d, 0x43}; ++ ++struct _GoodixTlsServer; ++ ++typedef void (*GoodixTlsServerSendCallback)(struct _GoodixTlsServer *self, ++ guint8 *data, guint16 length); ++ ++typedef void (*GoodixTlsServerConnectionCallback)(struct _GoodixTlsServer *self, ++ GError *error, ++ gpointer user_data); ++ ++typedef void (*GoodixTlsServerDecodedCallback)(struct _GoodixTlsServer *self, ++ guint8 *data, gsize length, ++ GError *error); ++ ++typedef struct _GoodixTlsServer ++{ ++ // This callback should be called when a TLS packet must be send to the ++ // device ++ // GoodixTlsServerSendCallback send_callback; ++ ++ // This callback should be called when the connection is established. The ++ // error should be NULL. It can also be called when the connection fail. In ++ // this case, the error should not be NULL. ++ GoodixTlsServerConnectionCallback connection_callback; ++ ++ // This callback should be called when a TLS packet is decoded. The error ++ // should be NULL. ++ // It can also be called when the server fail to decode a packet. In this ++ // case, the error should not be NULL. ++ // GoodixTlsServerDecodedCallback decoded_callback; ++ ++ // Put what you need here. ++ gpointer user_data; // Passed to all callbacks ++ SSL_CTX *ssl_ctx; ++ int sock_fd; ++ SSL *ssl_layer; ++ // SSL* cli_ssl_layer; ++ int client_fd; ++ pthread_t serve_thread; ++} GoodixTlsServer; ++ ++// This is called only once to init the TLS server. ++// Return TRUE on success, FALSE otherwise and error should be set. ++gboolean goodix_tls_server_init(GoodixTlsServer *self, GError **error); ++ ++gboolean goodix_tls_init_cli(GoodixTlsServer *self, GError **err); ++ ++// This can be called multiple times. It is called when the device send a TLS ++// packet. ++int goodix_tls_server_receive(GoodixTlsServer *self, guint8 *data, ++ guint32 length, GError **error); ++ ++int goodix_tls_client_send(GoodixTlsServer *self, guint8 *data, guint16 length); ++ ++int goodix_tls_client_recv(GoodixTlsServer *self, guint8 *data, guint16 length); ++ ++// This is called only once to deinit the TLS server. ++// Return TRUE on success, FALSE otherwise and error should be set. ++gboolean goodix_tls_server_deinit(GoodixTlsServer *self, GError **error); +diff --git a/libfprint/fpi-print.h b/libfprint/fpi-print.h +index fb388097..f3e72b40 100644 +--- a/libfprint/fpi-print.h ++++ b/libfprint/fpi-print.h +@@ -42,9 +42,9 @@ gboolean fpi_print_add_from_image (FpPrint *print, + FpImage *image, + GError **error); + +-FpiMatchResult fpi_print_bz3_match (FpPrint * template, +- FpPrint * print, +- gint bz3_threshold, ++FpiMatchResult fpi_print_bz3_match (FpPrint *temp, ++ FpPrint *print, ++ gint bz3_threshold, + GError **error); + + /* Helpers to encode metadata into user ID strings. */ +diff --git a/libfprint/fprint-list-udev-hwdb.c b/libfprint/fprint-list-udev-hwdb.c +index 16b6d18f..b768d610 100644 +--- a/libfprint/fprint-list-udev-hwdb.c ++++ b/libfprint/fprint-list-udev-hwdb.c +@@ -88,19 +88,16 @@ static const FpIdEntry whitelist_id_table[] = { + { .vid = 0x1c7a, .pid = 0x0575 }, + { .vid = 0x1c7a, .pid = 0x0576 }, + { .vid = 0x27c6, .pid = 0x5042 }, +- { .vid = 0x27c6, .pid = 0x5110 }, + { .vid = 0x27c6, .pid = 0x5117 }, + { .vid = 0x27c6, .pid = 0x5125 }, + { .vid = 0x27c6, .pid = 0x5201 }, + { .vid = 0x27c6, .pid = 0x521d }, + { .vid = 0x27c6, .pid = 0x5301 }, + { .vid = 0x27c6, .pid = 0x530c }, +- { .vid = 0x27c6, .pid = 0x532d }, + { .vid = 0x27c6, .pid = 0x533c }, + { .vid = 0x27c6, .pid = 0x5381 }, + { .vid = 0x27c6, .pid = 0x5385 }, + { .vid = 0x27c6, .pid = 0x538c }, +- { .vid = 0x27c6, .pid = 0x538d }, + { .vid = 0x27c6, .pid = 0x5395 }, + { .vid = 0x27c6, .pid = 0x5503 }, + { .vid = 0x27c6, .pid = 0x5584 }, +diff --git a/libfprint/meson.build b/libfprint/meson.build +index 25ed10f3..6ee02900 100644 +--- a/libfprint/meson.build ++++ b/libfprint/meson.build +@@ -139,6 +139,10 @@ driver_sources = { + [ 'drivers/synaptics/synaptics.c', 'drivers/synaptics/bmkt_message.c' ], + 'goodixmoc' : + [ 'drivers/goodixmoc/goodix.c', 'drivers/goodixmoc/goodix_proto.c' ], ++ 'goodixtls511' : ++ [ 'drivers/goodixtls/goodix511.c' ], ++ 'goodixtls53xd' : ++ [ 'drivers/goodixtls/goodix53xd.c' ] + } + + helper_sources = { +@@ -148,6 +152,8 @@ helper_sources = { + [ 'drivers/aesx660.c' ], + 'aes3k' : + [ 'drivers/aes3k.c' ], ++ 'goodixtls' : ++ [ 'drivers/goodixtls/goodix_proto.c', 'drivers/goodixtls/goodix.c', 'drivers/goodixtls/goodixtls.c' ], + 'nss' : + [ ], + 'udev' : +diff --git a/meson.build b/meson.build +index 823728c7..ac31a3d8 100644 +--- a/meson.build ++++ b/meson.build +@@ -124,6 +124,8 @@ default_drivers = [ + 'upeksonly', + 'upekts', + 'goodixmoc', ++ 'goodixtls511', ++ 'goodixtls53xd', + 'nb1010', + + # SPI +@@ -157,6 +159,8 @@ driver_helper_mapping = { + 'aes4000' : [ 'aeslib', 'aes3k' ], + 'uru4000' : [ 'nss' ], + 'elanspi' : [ 'udev' ], ++ 'goodixtls511' : [ 'goodixtls' ], ++ 'goodixtls53xd' : [ 'goodixtls' ], + 'virtual_image' : [ 'virtual' ], + 'virtual_device' : [ 'virtual' ], + 'virtual_device_storage' : [ 'virtual' ], +@@ -212,6 +216,18 @@ foreach i : driver_helpers + + libfprint_conf.set10('HAVE_PIXMAN', true) + optional_deps += imaging_dep ++ elif i == 'goodixtls' ++ openssl_dep = dependency('openssl', required: false) ++ if not openssl_dep.found() ++ error('openssl is required for @0@ and possibly others'.format(driver)) ++ endif ++ optional_deps += openssl_dep ++ ++ threads_dep = dependency('threads', required: false) ++ if not threads_dep.found() ++ error('threads is required for @0@ and possibly others'.format(driver)) ++ endif ++ optional_deps += threads_dep + elif i == 'nss' + nss_dep = dependency('nss', required: false) + if not nss_dep.found() +diff --git a/scripts/uncrustify.cfg b/scripts/uncrustify.cfg +index 1dbd3bad..34b9a358 100644 +--- a/scripts/uncrustify.cfg ++++ b/scripts/uncrustify.cfg +@@ -120,7 +120,7 @@ nl_multi_line_cond true + # Not clear what to do about that... + mod_full_brace_for Remove + mod_full_brace_if Remove +-mod_full_brace_if_chain True ++mod_full_brace_if_chain 1 + mod_full_brace_while Remove + mod_full_brace_do Remove + mod_full_brace_nl 3 +diff --git a/tests/goodixmoc/custom.pcapng b/tests/goodixmoc/custom.pcapng +index eb58d865..b5e2d89c 100644 +Binary files a/tests/goodixmoc/custom.pcapng and b/tests/goodixmoc/custom.pcapng differ +diff --git a/tests/goodixmoc/custom.py b/tests/goodixmoc/custom.py +index 1fb513a1..38fdd26c 100755 +--- a/tests/goodixmoc/custom.py ++++ b/tests/goodixmoc/custom.py +@@ -25,6 +25,9 @@ + + d.open_sync() + ++# 1. verify clear storage command, 2. make sure later asserts are good ++d.clear_storage_sync() ++ + template = FPrint.Print.new(d) + + def enroll_progress(*args): +diff --git a/tests/goodixmoc/device b/tests/goodixmoc/device +index 9fb39e59..1e209a1d 100644 +--- a/tests/goodixmoc/device ++++ b/tests/goodixmoc/device +@@ -1,14 +1,14 @@ + P: /devices/pci0000:00/0000:00:14.0/usb1/1-3 +-N: bus/usb/001/053=12010002EF000040C627966400010102030109022000010103A0320904000002FF0000040705830240000007050102400000 +-E: DEVNAME=/dev/bus/usb/001/053 ++N: bus/usb/001/023=12010002EF000040C627966400010102030109022000010103A0320904000002FF0000040705830240000007050102400000 ++E: DEVNAME=/dev/bus/usb/001/023 + E: DEVTYPE=usb_device + E: DRIVER=usb + E: PRODUCT=27c6/6496/100 + E: TYPE=239/0/0 + E: BUSNUM=001 +-E: DEVNUM=053 ++E: DEVNUM=023 + E: MAJOR=189 +-E: MINOR=52 ++E: MINOR=22 + E: SUBSYSTEM=usb + E: ID_VENDOR=Goodix_Technology_Co.__Ltd. + E: ID_VENDOR_ENC=Goodix\x20Technology\x20Co.\x2c\x20Ltd. +@@ -23,6 +23,7 @@ E: ID_BUS=usb + E: ID_USB_INTERFACES=:ff0000: + E: ID_VENDOR_FROM_DATABASE=Shenzhen Goodix Technology Co.,Ltd. + E: ID_AUTOSUSPEND=1 ++E: ID_PERSIST=0 + E: ID_PATH=pci-0000:00:14.0-usb-0:3 + E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_3 + A: authorized=1\n +@@ -40,8 +41,8 @@ A: bmAttributes=a0\n + A: busnum=1\n + A: configuration=XXXX_MOC_B0\n + H: descriptors=12010002EF000040C627966400010102030109022000010103A0320904000002FF0000040705830240000007050102400000 +-A: dev=189:52\n +-A: devnum=53\n ++A: dev=189:22\n ++A: devnum=23\n + A: devpath=3\n + L: driver=../../../../../bus/usb/drivers/usb + L: firmware_node=../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1c/device:1d/device:20 +@@ -51,16 +52,16 @@ A: ltm_capable=no\n + A: manufacturer=Goodix Technology Co., Ltd.\n + A: maxchild=0\n + L: port=../1-0:1.0/usb1-port3 +-A: power/active_duration=29262\n ++A: power/active_duration=22667\n + A: power/autosuspend=2\n + A: power/autosuspend_delay_ms=2000\n +-A: power/connected_duration=57399\n ++A: power/connected_duration=917616\n + A: power/control=auto\n + A: power/level=auto\n + A: power/persist=1\n +-A: power/runtime_active_time=29308\n ++A: power/runtime_active_time=22809\n + A: power/runtime_status=active\n +-A: power/runtime_suspended_time=27850\n ++A: power/runtime_suspended_time=894564\n + A: power/wakeup=disabled\n + A: power/wakeup_abort_count=\n + A: power/wakeup_active=\n +@@ -77,29 +78,29 @@ A: rx_lanes=1\n + A: serial=XXXX_MOC_B0\n + A: speed=12\n + A: tx_lanes=1\n +-A: urbnum=394\n ++A: urbnum=298\n + A: version= 2.00\n + + P: /devices/pci0000:00/0000:00:14.0/usb1 +-N: bus/usb/001/001=12010002090001406B1D020013050302010109021900010100E0000904000001090000000705810304000C ++N: bus/usb/001/001=12010002090001406B1D020017050302010109021900010100E0000904000001090000000705810304000C + E: DEVNAME=/dev/bus/usb/001/001 + E: DEVTYPE=usb_device + E: DRIVER=usb +-E: PRODUCT=1d6b/2/513 ++E: PRODUCT=1d6b/2/517 + E: TYPE=9/0/1 + E: BUSNUM=001 + E: DEVNUM=001 + E: MAJOR=189 + E: MINOR=0 + E: SUBSYSTEM=usb +-E: ID_VENDOR=Linux_5.13.15-200.fc34.x86_64_xhci-hcd +-E: ID_VENDOR_ENC=Linux\x205.13.15-200.fc34.x86_64\x20xhci-hcd ++E: ID_VENDOR=Linux_5.17.12-300.fc36.x86_64_xhci-hcd ++E: ID_VENDOR_ENC=Linux\x205.17.12-300.fc36.x86_64\x20xhci-hcd + E: ID_VENDOR_ID=1d6b + E: ID_MODEL=xHCI_Host_Controller + E: ID_MODEL_ENC=xHCI\x20Host\x20Controller + E: ID_MODEL_ID=0002 +-E: ID_REVISION=0513 +-E: ID_SERIAL=Linux_5.13.15-200.fc34.x86_64_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 ++E: ID_REVISION=0517 ++E: ID_SERIAL=Linux_5.17.12-300.fc36.x86_64_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 + E: ID_SERIAL_SHORT=0000:00:14.0 + E: ID_BUS=usb + E: ID_USB_INTERFACES=:090000: +@@ -122,11 +123,11 @@ A: bMaxPacketSize0=64\n + A: bMaxPower=0mA\n + A: bNumConfigurations=1\n + A: bNumInterfaces= 1\n +-A: bcdDevice=0513\n ++A: bcdDevice=0517\n + A: bmAttributes=e0\n + A: busnum=1\n + A: configuration=\n +-H: descriptors=12010002090001406B1D020013050302010109021900010100E0000904000001090000000705810304000C ++H: descriptors=12010002090001406B1D020017050302010109021900010100E0000904000001090000000705810304000C + A: dev=189:0\n + A: devnum=1\n + A: devpath=0\n +@@ -136,15 +137,15 @@ A: idProduct=0002\n + A: idVendor=1d6b\n + A: interface_authorized_default=1\n + A: ltm_capable=no\n +-A: manufacturer=Linux 5.13.15-200.fc34.x86_64 xhci-hcd\n ++A: manufacturer=Linux 5.17.12-300.fc36.x86_64 xhci-hcd\n + A: maxchild=12\n +-A: power/active_duration=219578717\n ++A: power/active_duration=164289796\n + A: power/autosuspend=0\n + A: power/autosuspend_delay_ms=0\n +-A: power/connected_duration=219649620\n ++A: power/connected_duration=164360220\n + A: power/control=auto\n + A: power/level=auto\n +-A: power/runtime_active_time=219589127\n ++A: power/runtime_active_time=164331876\n + A: power/runtime_status=active\n + A: power/runtime_suspended_time=0\n + A: power/wakeup=disabled\n +@@ -163,14 +164,14 @@ A: rx_lanes=1\n + A: serial=0000:00:14.0\n + A: speed=480\n + A: tx_lanes=1\n +-A: urbnum=4325\n ++A: urbnum=2097\n + A: version= 2.00\n + + P: /devices/pci0000:00/0000:00:14.0 + E: DRIVER=xhci_hcd + E: PCI_CLASS=C0330 + E: PCI_ID=8086:9DED +-E: PCI_SUBSYS_ID=17AA:2292\n ++E: PCI_SUBSYS_ID=17AA:2292 + E: PCI_SLOT_NAME=0000:00:14.0 + E: MODALIAS=pci:v00008086d00009DEDsv000017AAsd00002292bc0Csc03i30 + E: SUBSYSTEM=pci +@@ -183,7 +184,7 @@ E: ID_MODEL_FROM_DATABASE=Cannon Point-LP USB 3.1 xHCI Controller + A: ari_enabled=0\n + A: broken_parity_status=0\n + A: class=0x0c0330\n +-H: config=8680ED9D060490021130030C00008000040022EA000000000000000000000000000000000000000000000000AA179222000000007000000000000000FF010000FD0134808FC6FF8300000000000000007F6DDC0F000000004C084B0100000000316000000000000000000000000000000180C2C1080000000000000000000000059087001803E0FE0000000000000000090014F01000400100000000C10A080000080E00001800008F40020000010000000000000000000008000000040000000000000000000000000000000000000000000000000000000800000004000000000000000000000000000000000000000000000000000000B50F320112000000 ++H: config=8680ED9D060490021130030C00008000040022EA000000000000000000000000000000000000000000000000AA179222000000007000000000000000FF010000FD0134808FC6FF8300000000000000007F6DDC0F00000000F507312600000000316000000000000000000000000000000180C2C1080000000000000000000000059087001803E0FE0000000000000000090014F01000400100000000C10A080000080E00001800008F40020000010000000000000000000008000000040000000000000000000000000000000000000000000000000000000800000004000000000000000000000000000000000000000000000000000000B50F320112000000 + A: consistent_dma_mask_bits=64\n + A: d3cold_allowed=1\n + A: dbc=disabled\n +@@ -201,8 +202,8 @@ A: msi_bus=1\n + A: msi_irqs/128=msi\n + A: numa_node=-1\n + A: pools=poolinfo - 0.1\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 11 12 2112 12\nxHCI ring segments 46 50 4096 50\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 6 32 128 1\nbuffer-32 0 0 32 0\n +-A: power/control=on\n +-A: power/runtime_active_time=219589302\n ++A: power/control=auto\n ++A: power/runtime_active_time=164332777\n + A: power/runtime_status=active\n + A: power/runtime_suspended_time=0\n + A: power/wakeup=enabled\n