libfprint/libfprint/drivers/goodixmoc/goodix.c
Marco Trevisan (Treviño) ef805f2341 device: Expose supported features publicly as FpDeviceFeature
It can be convenient for device users to check what it supports, without
having multiple functions to check each single feature.

So expose this and add tests.
2021-04-12 22:14:06 +02:00

1586 lines
47 KiB
C

/*
* Goodix Moc driver for libfprint
* Copyright (C) 2019 Shenzhen Goodix Technology Co., Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define FP_COMPONENT "goodixmoc"
#include "drivers_api.h"
#include "goodix_proto.h"
#include "goodix.h"
/* Default enroll stages number */
#define DEFAULT_ENROLL_SAMPLES 8
/* Usb port setting */
#define EP_IN (3 | FPI_USB_ENDPOINT_IN)
#define EP_OUT (1 | FPI_USB_ENDPOINT_OUT)
#define EP_IN_MAX_BUF_SIZE (2048)
#define MAX_USER_ID_LEN (64)
/* Command transfer timeout :ms*/
#define CMD_TIMEOUT (1000)
#define ACK_TIMEOUT (2000)
#define DATA_TIMEOUT (5000)
struct _FpiDeviceGoodixMoc
{
FpDevice parent;
FpiSsm *task_ssm;
FpiSsm *cmd_ssm;
FpiUsbTransfer *cmd_transfer;
gboolean cmd_cancelable;
pgxfp_sensor_cfg_t sensorcfg;
gint enroll_stage;
gint max_enroll_stage;
gint max_stored_prints;
GCancellable *cancellable;
GPtrArray *list_result;
guint8 template_id[TEMPLATE_ID_SIZE];
gboolean is_enroll_identify;
gboolean is_power_button_shield_on;
};
G_DEFINE_TYPE (FpiDeviceGoodixMoc, fpi_device_goodixmoc, FP_TYPE_DEVICE)
typedef void (*SynCmdMsgCallback) (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error);
typedef struct
{
guint8 cmd;
SynCmdMsgCallback callback;
} CommandData;
static gboolean parse_print_data (GVariant *data,
guint8 *finger,
const guint8 **tid,
gsize *tid_len,
const guint8 **user_id,
gsize *user_id_len);
/******************************************************************************
*
* fp_cmd_xxx Function
*
*****************************************************************************/
static void
fp_cmd_receive_cb (FpiUsbTransfer *transfer,
FpDevice *device,
gpointer user_data,
GError *error)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (device);
CommandData *data = user_data;
int ret = -1, ssm_state = 0;
gxfp_cmd_response_t cmd_reponse = {0, };
pack_header header;
guint32 crc32_calc = 0;
guint16 cmd = 0;
if (error)
{
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
if (data == NULL)
{
fpi_ssm_mark_failed (transfer->ssm,
fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
return;
}
ssm_state = fpi_ssm_get_cur_state (transfer->ssm);
/* skip zero length package */
if (transfer->actual_length == 0)
{
fpi_ssm_jump_to_state (transfer->ssm, ssm_state);
return;
}
ret = gx_proto_parse_header (transfer->buffer, transfer->actual_length, &header);
if (ret != 0)
{
fpi_ssm_mark_failed (transfer->ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"Corrupted message received"));
return;
}
gx_proto_crc32_calc (transfer->buffer, PACKAGE_HEADER_SIZE + header.len, (uint8_t *) &crc32_calc);
if(crc32_calc != GUINT32_FROM_LE (*(uint32_t *) (transfer->buffer + PACKAGE_HEADER_SIZE + header.len)))
{
fpi_ssm_mark_failed (transfer->ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"Package crc check failed"));
return;
}
cmd = MAKE_CMD_EX (header.cmd0, header.cmd1);
ret = gx_proto_parse_body (cmd, &transfer->buffer[PACKAGE_HEADER_SIZE], header.len, &cmd_reponse);
if (ret != 0)
{
fpi_ssm_mark_failed (transfer->ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"Corrupted message received"));
return;
}
/* ack */
if(header.cmd0 == RESPONSE_PACKAGE_CMD)
{
if (data->cmd != cmd_reponse.parse_msg.ack_cmd)
{
fpi_ssm_mark_failed (transfer->ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"Unexpected response, got 0x%x",
cmd_reponse.parse_msg.ack_cmd));
return;
}
fpi_ssm_next_state (transfer->ssm);
return;
}
/* data */
if (data->cmd != header.cmd0)
{
fpi_ssm_mark_failed (transfer->ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"Unexpected cmd, got 0x%x",
header.cmd0));
return;
}
if (data->callback)
data->callback (self, &cmd_reponse, NULL);
fpi_ssm_mark_completed (transfer->ssm);
}
static void
fp_cmd_run_state (FpiSsm *ssm,
FpDevice *dev)
{
FpiUsbTransfer *transfer;
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (dev);
switch (fpi_ssm_get_cur_state (ssm))
{
case FP_CMD_SEND:
if (self->cmd_transfer)
{
self->cmd_transfer->ssm = ssm;
fpi_usb_transfer_submit (g_steal_pointer (&self->cmd_transfer),
CMD_TIMEOUT,
NULL,
fpi_ssm_usb_transfer_cb,
NULL);
}
else
{
fpi_ssm_next_state (ssm);
}
break;
case FP_CMD_GET_ACK:
transfer = fpi_usb_transfer_new (dev);
transfer->ssm = ssm;
fpi_usb_transfer_fill_bulk (transfer, EP_IN, EP_IN_MAX_BUF_SIZE);
fpi_usb_transfer_submit (transfer,
ACK_TIMEOUT,
NULL,
fp_cmd_receive_cb,
fpi_ssm_get_data (ssm));
break;
case FP_CMD_GET_DATA:
transfer = fpi_usb_transfer_new (dev);
transfer->ssm = ssm;
fpi_usb_transfer_fill_bulk (transfer, EP_IN, EP_IN_MAX_BUF_SIZE);
fpi_usb_transfer_submit (transfer,
self->cmd_cancelable ? 0 : DATA_TIMEOUT,
self->cmd_cancelable ? self->cancellable : NULL,
fp_cmd_receive_cb,
fpi_ssm_get_data (ssm));
break;
}
}
static void
fp_cmd_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (dev);
CommandData *data = fpi_ssm_get_data (ssm);
self->cmd_ssm = NULL;
/* Notify about the SSM failure from here instead. */
if (error)
{
if (data->callback)
data->callback (self, NULL, error);
else
g_error_free (error);
}
}
static FpiUsbTransfer *
alloc_cmd_transfer (FpDevice *dev,
guint8 cmd0,
guint8 cmd1,
const guint8 *data,
guint16 data_len)
{
gint ret = -1;
g_autoptr(FpiUsbTransfer) transfer = NULL;
guint32 total_len = data_len + PACKAGE_HEADER_SIZE + PACKAGE_CRC_SIZE;
g_return_val_if_fail (data || data_len == 0, NULL);
transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_bulk (transfer, EP_OUT, total_len);
ret = gx_proto_build_package (transfer->buffer, &total_len, MAKE_CMD_EX (cmd0, cmd1), data, data_len);
g_return_val_if_fail (ret == 0, NULL);
return g_steal_pointer (&transfer);
}
static void
fp_cmd_ssm_done_data_free (CommandData *data)
{
g_free (data);
}
static void
goodix_sensor_cmd (FpiDeviceGoodixMoc *self,
guint8 cmd0,
guint8 cmd1,
gboolean bwait_data_delay,
const guint8 * payload,
gssize payload_len,
SynCmdMsgCallback callback)
{
g_autoptr(FpiUsbTransfer) transfer = NULL;
CommandData *data = g_new0 (CommandData, 1);
transfer = alloc_cmd_transfer (FP_DEVICE (self), cmd0, cmd1, payload, payload_len);
data->cmd = cmd0;
data->callback = callback;
self->cmd_transfer = g_steal_pointer (&transfer);
self->cmd_cancelable = bwait_data_delay;
self->cmd_ssm = fpi_ssm_new (FP_DEVICE (self),
fp_cmd_run_state,
FP_CMD_NUM_STATES);
fpi_ssm_set_data (self->cmd_ssm, data, (GDestroyNotify) fp_cmd_ssm_done_data_free);
fpi_ssm_start (self->cmd_ssm, fp_cmd_ssm_done);
}
/******************************************************************************
*
* fp_pwr_btn_shield_cb Function
*
*****************************************************************************/
static void
fp_pwr_btn_shield_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
if (resp->result >= GX_FAILED)
{
fp_dbg ("Setting power button shield failed, result: 0x%x", resp->result);
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL));
return;
}
if (resp->power_button_shield_resp.resp_cmd1 == MOC_CMD1_PWR_BTN_SHIELD_ON)
self->is_power_button_shield_on = true;
else
self->is_power_button_shield_on = false;
fpi_ssm_next_state (self->task_ssm);
}
/******************************************************************************
*
* fp_verify_xxxx Function
*
*****************************************************************************/
static void
fp_verify_capture_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
if (resp->result >= GX_FAILED)
{
fp_dbg ("Capture sample failed, result: 0x%x", resp->result);
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL));
return;
}
fpi_device_report_finger_status_changes (FP_DEVICE (self),
FP_FINGER_STATUS_PRESENT,
FP_FINGER_STATUS_NONE);
if (resp->capture_data_resp.img_quality == 0)
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_retry_new (FP_DEVICE_RETRY_REMOVE_FINGER));
return;
}
else if (resp->capture_data_resp.img_coverage < 35)
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_retry_new (FP_DEVICE_RETRY_CENTER_FINGER));
return;
}
fpi_ssm_next_state (self->task_ssm);
}
static void
fp_verify_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
g_autoptr(GPtrArray) templates = NULL;
FpDevice *device = FP_DEVICE (self);
FpPrint *print = NULL;
gint cnt = 0;
gboolean find = false;
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
if (resp->verify.match)
{
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY)
{
templates = g_ptr_array_sized_new (1);
fpi_device_get_verify_data (device, &print);
g_ptr_array_add (templates, print);
}
else
{
fpi_device_get_identify_data (device, &templates);
g_ptr_array_ref (templates);
}
for (cnt = 0; cnt < templates->len; cnt++)
{
g_autoptr(GVariant) data = NULL;
guint8 finger;
const guint8 *user_id;
gsize user_id_len = 0;
const guint8 *tid;
gsize tid_len = 0;
print = g_ptr_array_index (templates, cnt);
g_object_get (print, "fpi-data", &data, NULL);
if (!parse_print_data (data, &finger, &tid, &tid_len, &user_id, &user_id_len))
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID,
"Parse print error"));
return;
}
if (memcmp (&resp->verify.template.tid, tid, TEMPLATE_ID_SIZE) == 0)
{
find = true;
break;
}
}
if (find)
{
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY)
fpi_device_verify_report (device, FPI_MATCH_SUCCESS, NULL, error);
else
fpi_device_identify_report (device, print, print, error);
}
}
if (!find)
{
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY)
fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, error);
else
fpi_device_identify_report (device, NULL, NULL, error);
}
fpi_ssm_next_state (self->task_ssm);
}
static void
fp_verify_sm_run_state (FpiSsm *ssm, FpDevice *device)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (device);
guint8 param[3] = { 0 };
guint8 nonce[TEMPLATE_ID_SIZE] = { 0 };
param[0] = 0x01;
param[1] = self->sensorcfg->config[10];
param[2] = self->sensorcfg->config[11];
switch (fpi_ssm_get_cur_state (ssm))
{
case FP_VERIFY_PWR_BTN_SHIELD_ON:
goodix_sensor_cmd (self, MOC_CMD0_PWR_BTN_SHIELD, MOC_CMD1_PWR_BTN_SHIELD_ON,
false,
NULL,
0,
fp_pwr_btn_shield_cb);
break;
case FP_VERIFY_CAPTURE:
fpi_device_report_finger_status_changes (device,
FP_FINGER_STATUS_NEEDED,
FP_FINGER_STATUS_NONE);
goodix_sensor_cmd (self, MOC_CMD0_CAPTURE_DATA, MOC_CMD1_DEFAULT,
true,
(const guint8 *) &param,
G_N_ELEMENTS (param),
fp_verify_capture_cb);
break;
case FP_VERIFY_IDENTIFY:
goodix_sensor_cmd (self, MOC_CMD0_IDENTIFY, MOC_CMD1_DEFAULT,
false,
(const guint8 *) nonce,
TEMPLATE_ID_SIZE,
fp_verify_cb);
break;
case FP_VERIFY_PWR_BTN_SHIELD_OFF:
goodix_sensor_cmd (self, MOC_CMD0_PWR_BTN_SHIELD, MOC_CMD1_PWR_BTN_SHIELD_OFF,
false,
NULL,
0,
fp_pwr_btn_shield_cb);
break;
}
}
static void
fp_verify_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (dev);
fp_info ("Verify complete!");
if (error && error->domain == FP_DEVICE_RETRY)
{
if (fpi_device_get_current_action (dev) == FPI_DEVICE_ACTION_VERIFY)
fpi_device_verify_report (dev, FPI_MATCH_ERROR, NULL, g_steal_pointer (&error));
else
fpi_device_identify_report (dev, NULL, NULL, g_steal_pointer (&error));
}
if (fpi_device_get_current_action (dev) == FPI_DEVICE_ACTION_VERIFY)
fpi_device_verify_complete (dev, error);
else
fpi_device_identify_complete (dev, error);
self->task_ssm = NULL;
}
/******************************************************************************
*
* fp__xxxx Function
*
*****************************************************************************/
static gboolean
encode_finger_id (
const guint8 * tid,
guint16 tid_len,
const guint8 * uid,
guint16 uid_len,
guint8 ** fid,
guint16 * fid_len
)
{
guint8 * buffer = NULL;
guint16 offset = 0;
g_return_val_if_fail (tid != NULL, FALSE);
g_return_val_if_fail (uid != NULL, FALSE);
g_return_val_if_fail (fid != NULL, FALSE);
g_return_val_if_fail (fid_len != NULL, FALSE);
*fid_len = (guint16) (70 + uid_len); // must include fingerid length
*fid = (guint8 *) g_malloc0 (*fid_len + 2);
buffer = *fid;
offset = 0;
buffer[offset++] = LOBYTE (*fid_len);
buffer[offset++] = HIBYTE (*fid_len);
buffer[offset++] = 67;
buffer[offset++] = 1;
buffer[offset++] = 1; // finger index
buffer[offset++] = 0; //
offset += 32;
memcpy (&buffer[offset], tid, MIN (tid_len, TEMPLATE_ID_SIZE));
offset += 32; // offset == 68
buffer[offset++] = uid_len;
memcpy (&buffer[offset], uid, uid_len);
offset += (guint8) uid_len;
buffer[offset++] = 0;
if (offset != (*fid_len + 2))
{
memset (buffer, 0, *fid_len);
*fid_len = 0;
fp_err ("offset != fid_len, %d != %d", offset, *fid_len);
return FALSE;
}
*fid_len += 2;
return TRUE;
}
/******************************************************************************
*
* fp_enroll_xxxx Function
*
*****************************************************************************/
static void
fp_enroll_enum_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
if (resp->result != GX_SUCCESS)
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
"Failed to enumerate fingers, result: 0x%x",
resp->result));
return;
}
if (resp->finger_list_resp.finger_num >= self->max_stored_prints)
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new (FP_DEVICE_ERROR_DATA_FULL));
return;
}
fpi_ssm_jump_to_state (self->task_ssm, FP_ENROLL_CAPTURE);
}
static void
fp_enroll_identify_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
if (resp->verify.match)
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_DUPLICATE,
"Finger is too similar to another, try use a different finger"));
// maybe need fpi_device_enroll_report_message ...
return;
}
fpi_ssm_next_state (self->task_ssm);
}
static void
fp_enroll_init_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
memcpy (self->template_id, resp->enroll_init.tid, TEMPLATE_ID_SIZE);
fpi_ssm_next_state (self->task_ssm);
}
static void
fp_enroll_capture_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
/* */
if (resp->result >= GX_FAILED)
{
fp_warn ("Capture sample failed, result: 0x%x", resp->result);
fpi_device_enroll_progress (FP_DEVICE (self),
self->enroll_stage,
NULL,
fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL));
fpi_ssm_jump_to_state (self->task_ssm, FP_ENROLL_CAPTURE);
return;
}
fpi_device_report_finger_status_changes (FP_DEVICE (self),
FP_FINGER_STATUS_PRESENT,
FP_FINGER_STATUS_NONE);
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",
self->sensorcfg->config[4],
resp->capture_data_resp.img_quality,
self->sensorcfg->config[5],
resp->capture_data_resp.img_coverage);
fpi_device_enroll_progress (FP_DEVICE (self),
self->enroll_stage,
NULL,
fpi_device_retry_new (FP_DEVICE_RETRY_CENTER_FINGER));
fpi_ssm_jump_to_state (self->task_ssm, FP_ENROLL_CAPTURE);
return;
}
if (self->is_enroll_identify)
{
self->is_enroll_identify = false;
fpi_ssm_jump_to_state (self->task_ssm, FP_ENROLL_IDENTIFY);
}
else
{
fpi_ssm_next_state (self->task_ssm);
}
}
static void
fp_enroll_update_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
if (resp->enroll_update.img_preoverlay > self->sensorcfg->config[3])
{
fp_dbg ("Sample overlapping ratio is too High(%d): %d ",
self->sensorcfg->config[3],
resp->enroll_update.img_preoverlay);
/* here should tips move finger and try again */
fpi_device_enroll_progress (FP_DEVICE (self),
self->enroll_stage,
NULL,
fpi_device_retry_new (FP_DEVICE_RETRY_REMOVE_FINGER));
}
else if (resp->enroll_update.rollback)
{
fpi_device_enroll_progress (FP_DEVICE (self),
self->enroll_stage,
NULL,
fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL));
}
else
{
self->enroll_stage++;
fpi_device_enroll_progress (FP_DEVICE (self), self->enroll_stage, NULL, NULL);
}
/* if enroll complete, no need to wait finger up */
if (self->enroll_stage >= self->max_enroll_stage)
{
fpi_ssm_jump_to_state (self->task_ssm, FP_ENROLL_CHECK_DUPLICATE);
return;
}
fpi_ssm_next_state (self->task_ssm);
}
static void
fp_enroll_check_duplicate_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
if (resp->check_duplicate_resp.duplicate)
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_DUPLICATE,
"Finger has already enrolled"));
return;
}
fpi_ssm_next_state (self->task_ssm);
}
static void
fp_enroll_commit_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
if (resp->result >= GX_FAILED)
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"Commit template failed with errcode: 0x%x", resp->result));
return;
}
fpi_ssm_next_state (self->task_ssm);
}
static void
fp_finger_mode_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
/* if reach max timeout(5sec) finger not up, switch to finger up again */
if (resp->finger_status.status == GX_ERROR_WAIT_FINGER_UP_TIMEOUT)
{
fpi_ssm_jump_to_state (self->task_ssm, FP_ENROLL_WAIT_FINGER_UP);
return;
}
else if (resp->finger_status.status != GX_SUCCESS)
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"Switch finger mode failed"));
return;
}
fpi_device_report_finger_status_changes (FP_DEVICE (self),
FP_FINGER_STATUS_NONE,
FP_FINGER_STATUS_PRESENT);
if (self->enroll_stage < self->max_enroll_stage)
{
fpi_ssm_jump_to_state (self->task_ssm, FP_ENROLL_CAPTURE);
return;
}
fpi_ssm_next_state (self->task_ssm);
}
static void
fp_enroll_sm_run_state (FpiSsm *ssm, FpDevice *device)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (device);
FpPrint *print = NULL;
GVariant *data = NULL;
GVariant *uid = NULL;
GVariant *tid = NULL;
guint finger;
guint16 user_id_len;
guint16 payload_len = 0;
g_autofree gchar *user_id = NULL;
g_autofree guint8 *payload = NULL;
guint8 dummy[3] = { 0 };
dummy[1] = self->sensorcfg->config[4];
dummy[2] = self->sensorcfg->config[5];
switch (fpi_ssm_get_cur_state (ssm))
{
case FP_ENROLL_ENUM:
{
goodix_sensor_cmd (self, MOC_CMD0_GETFINGERLIST, MOC_CMD1_DEFAULT,
false,
(const guint8 *) &dummy,
1,
fp_enroll_enum_cb);
}
break;
case FP_ENROLL_PWR_BTN_SHIELD_ON:
{
goodix_sensor_cmd (self, MOC_CMD0_PWR_BTN_SHIELD, MOC_CMD1_PWR_BTN_SHIELD_ON,
false,
NULL,
0,
fp_pwr_btn_shield_cb);
}
break;
case FP_ENROLL_IDENTIFY:
{
dummy[0] = 0x01;
dummy[1] = self->sensorcfg->config[10];
dummy[2] = self->sensorcfg->config[11];
goodix_sensor_cmd (self, MOC_CMD0_IDENTIFY, MOC_CMD1_DEFAULT,
false,
(const guint8 *) &self->template_id,
TEMPLATE_ID_SIZE,
fp_enroll_identify_cb);
}
break;
case FP_ENROLL_CREATE:
{
goodix_sensor_cmd (self, MOC_CMD0_ENROLL_INIT, MOC_CMD1_DEFAULT,
false,
(const guint8 *) &dummy,
1,
fp_enroll_init_cb);
}
break;
case FP_ENROLL_CAPTURE:
fpi_device_report_finger_status_changes (device,
FP_FINGER_STATUS_NEEDED,
FP_FINGER_STATUS_NONE);
goodix_sensor_cmd (self, MOC_CMD0_CAPTURE_DATA, MOC_CMD1_DEFAULT,
true,
(const guint8 *) &dummy,
3,
fp_enroll_capture_cb);
break;
case FP_ENROLL_UPDATE:
dummy[0] = 1;
dummy[1] = self->sensorcfg->config[2];
dummy[2] = self->sensorcfg->config[3];
goodix_sensor_cmd (self, MOC_CMD0_ENROLL, MOC_CMD1_DEFAULT,
false,
(const guint8 *) &dummy,
3,
fp_enroll_update_cb);
break;
case FP_ENROLL_WAIT_FINGER_UP:
dummy[0] = 0;
goodix_sensor_cmd (self, MOC_CMD0_FINGER_MODE, MOC_CMD1_SET_FINGER_UP,
true,
(const guint8 *) &dummy,
1,
fp_finger_mode_cb);
break;
case FP_ENROLL_CHECK_DUPLICATE:
goodix_sensor_cmd (self, MOC_CMD0_CHECK4DUPLICATE, MOC_CMD1_DEFAULT,
false,
(const guint8 *) &dummy,
3,
fp_enroll_check_duplicate_cb);
break;
case FP_ENROLL_COMMIT:
{
fpi_device_get_enroll_data (device, &print);
user_id = fpi_print_generate_user_id (print);
user_id_len = strlen (user_id);
user_id_len = MIN (100, user_id_len);
finger = 1;
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
memset (self->template_id, 0, TEMPLATE_ID_SIZE);
uid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
user_id,
user_id_len,
1);
tid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
self->template_id,
TEMPLATE_ID_SIZE,
1);
data = g_variant_new ("(y@ay@ay)",
finger,
tid,
uid);
fpi_print_set_type (print, FPI_PRINT_RAW);
fpi_print_set_device_stored (print, TRUE);
g_object_set (print, "fpi-data", data, NULL);
g_object_set (print, "description", user_id, NULL);
g_debug ("user_id: %s, user_id_len: %d, finger: %d", user_id, user_id_len, finger);
if(!encode_finger_id (self->template_id,
TEMPLATE_ID_SIZE,
(guint8 *) user_id,
user_id_len,
&payload,
&payload_len))
{
fpi_ssm_mark_failed (ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"encode_finger_id failed"));
return;
}
goodix_sensor_cmd (self, MOC_CMD0_COMMITENROLLMENT, MOC_CMD1_DEFAULT,
false,
(const guint8 *) payload,
payload_len,
fp_enroll_commit_cb);
}
break;
case FP_ENROLL_PWR_BTN_SHIELD_OFF:
{
goodix_sensor_cmd (self, MOC_CMD0_PWR_BTN_SHIELD, MOC_CMD1_PWR_BTN_SHIELD_OFF,
false,
NULL,
0,
fp_pwr_btn_shield_cb);
}
break;
}
}
static void
fp_enroll_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (dev);
FpPrint *print = NULL;
if (error)
{
fpi_device_enroll_complete (dev, NULL, error);
return;
}
fp_info ("Enrollment complete!");
fpi_device_get_enroll_data (FP_DEVICE (self), &print);
fpi_device_enroll_complete (FP_DEVICE (self), g_object_ref (print), NULL);
self->task_ssm = NULL;
}
/******************************************************************************
*
* fp_init_xxxx Function
*
*****************************************************************************/
static void
fp_init_version_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
g_autofree gchar *fw_type = NULL;
g_autofree gchar *fw_version = NULL;
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
G_STATIC_ASSERT (sizeof (resp->version_info.fwtype) == 8);
G_STATIC_ASSERT (sizeof (resp->version_info.fwversion) == 8);
fw_type = g_strndup ((const char *) resp->version_info.fwtype, sizeof (resp->version_info.fwtype));
fp_info ("Firmware type: %s", fw_type);
if (g_strcmp0 (fw_type, "APP") != 0)
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_NOT_SUPPORTED,
"Please update firmware using fwupd"));
return;
}
fw_version = g_strndup ((const char *) resp->version_info.fwversion, sizeof (resp->version_info.fwversion));
fp_info ("Firmware version: %s", fw_version);
fpi_ssm_next_state (self->task_ssm);
}
static void
fp_init_config_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
self->max_stored_prints = resp->finger_config.max_stored_prints;
fpi_ssm_next_state (self->task_ssm);
}
static void
fp_init_sm_run_state (FpiSsm *ssm, FpDevice *device)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (device);
guint8 dummy = 0;
switch (fpi_ssm_get_cur_state (ssm))
{
case FP_INIT_VERSION:
goodix_sensor_cmd (self, MOC_CMD0_GET_VERSION, MOC_CMD1_DEFAULT,
false,
&dummy,
1,
fp_init_version_cb);
break;
case FP_INIT_CONFIG:
goodix_sensor_cmd (self, MOC_CMD0_UPDATE_CONFIG, MOC_CMD1_WRITE_CFG_TO_FLASH,
false,
(guint8 *) self->sensorcfg,
sizeof (gxfp_sensor_cfg_t),
fp_init_config_cb);
break;
}
}
static void
fp_init_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (dev);
if (error)
{
fpi_device_open_complete (dev, error);
return;
}
self->task_ssm = NULL;
fpi_device_open_complete (dev, NULL);
}
/******************************************************************************
*
* fp_template_delete Function
*
*****************************************************************************/
static gboolean
parse_print_data (GVariant *data,
guint8 *finger,
const guint8 **tid,
gsize *tid_len,
const guint8 **user_id,
gsize *user_id_len)
{
g_autoptr(GVariant) user_id_var = NULL;
g_autoptr(GVariant) tid_var = NULL;
g_return_val_if_fail (data != NULL, FALSE);
g_return_val_if_fail (finger != NULL, FALSE);
g_return_val_if_fail (tid != NULL, FALSE);
g_return_val_if_fail (tid_len != NULL, FALSE);
g_return_val_if_fail (user_id != NULL, FALSE);
g_return_val_if_fail (user_id_len != NULL, FALSE);
*tid = NULL;
*tid_len = 0;
*user_id = NULL;
*user_id_len = 0;
if (!g_variant_check_format_string (data, "(y@ay@ay)", FALSE))
return FALSE;
g_variant_get (data,
"(y@ay@ay)",
finger,
&tid_var,
&user_id_var);
*tid = g_variant_get_fixed_array (tid_var, tid_len, 1);
*user_id = g_variant_get_fixed_array (user_id_var, user_id_len, 1);
if (*user_id_len == 0 || *user_id_len > 100)
return FALSE;
if (*user_id_len <= 0 || *user_id[0] == ' ')
return FALSE;
if(*tid_len != TEMPLATE_ID_SIZE)
return FALSE;
return TRUE;
}
static void
fp_template_delete_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
FpDevice *device = FP_DEVICE (self);
if (error)
{
fpi_device_delete_complete (device, error);
return;
}
if ((resp->result >= GX_FAILED) && (resp->result != GX_ERROR_FINGER_ID_NOEXIST))
{
fpi_device_delete_complete (FP_DEVICE (self),
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
"Failed delete enrolled users, result: 0x%x",
resp->result));
return;
}
fp_info ("Successfully deleted enrolled user");
fpi_device_delete_complete (device, NULL);
}
/******************************************************************************
*
* fp_template_list Function
*
*****************************************************************************/
static void
fp_template_list_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
FpDevice *device = FP_DEVICE (self);
if (error)
{
fpi_device_list_complete (FP_DEVICE (self), NULL, error);
return;
}
if (resp->result != GX_SUCCESS)
{
fp_info ("Failed to query enrolled users: %d", resp->result);
fpi_device_list_complete (FP_DEVICE (self),
NULL,
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
"Failed to query enrolled users, result: 0x%x",
resp->result));
return;
}
self->list_result = g_ptr_array_new_with_free_func (g_object_unref);
if(resp->finger_list_resp.finger_num == 0)
{
fp_info ("Database is empty");
fpi_device_list_complete (device,
g_steal_pointer (&self->list_result),
NULL);
return;
}
for (int n = 0; n < resp->finger_list_resp.finger_num; n++)
{
GVariant *data = NULL;
GVariant *tid = NULL;
GVariant *uid = NULL;
FpPrint *print;
gchar *userid;
userid = (gchar *) resp->finger_list_resp.finger_list[n].payload.data;
print = fp_print_new (FP_DEVICE (self));
tid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
resp->finger_list_resp.finger_list[n].tid,
TEMPLATE_ID_SIZE,
1);
uid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
resp->finger_list_resp.finger_list[n].payload.data,
resp->finger_list_resp.finger_list[n].payload.size,
1);
data = g_variant_new ("(y@ay@ay)",
resp->finger_list_resp.finger_list[n].finger_index,
tid,
uid);
fpi_print_set_type (print, FPI_PRINT_RAW);
fpi_print_set_device_stored (print, TRUE);
g_object_set (print, "fpi-data", data, NULL);
g_object_set (print, "description", userid, NULL);
fpi_print_fill_from_user_id (print, userid);
g_ptr_array_add (self->list_result, g_object_ref_sink (print));
}
fp_info ("Query complete!");
fpi_device_list_complete (device,
g_steal_pointer (&self->list_result),
NULL);
}
/******************************************************************************
*
* Interface Function
*
*****************************************************************************/
static void
gx_fp_probe (FpDevice *device)
{
GUsbDevice *usb_dev;
GError *error = NULL;
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (device);
g_autofree gchar *serial = NULL;
gint productid = 0;
/* Claim usb interface */
usb_dev = fpi_device_get_usb_device (device);
if (!g_usb_device_open (usb_dev, &error))
{
fpi_device_probe_complete (device, NULL, NULL, error);
return;
}
if (!g_usb_device_reset (usb_dev, &error))
goto err_close;
if (!g_usb_device_claim_interface (usb_dev, 0, 0, &error))
goto err_close;
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
{
serial = g_strdup ("emulated-device");
}
else
{
serial = g_usb_device_get_string_descriptor (usb_dev,
g_usb_device_get_serial_number_index (usb_dev),
&error);
if (serial && !g_str_has_suffix (serial, "B0"))
fp_warn ("Device with serial %s not supported", serial);
if (error)
{
g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (device)),
0, 0, NULL);
goto err_close;
}
}
productid = g_usb_device_get_pid (usb_dev);
switch (productid)
{
case 0x6496:
case 0x60A2:
self->max_enroll_stage = 12;
break;
default:
self->max_enroll_stage = DEFAULT_ENROLL_SAMPLES;
break;
}
fpi_device_set_nr_enroll_stages (device, self->max_enroll_stage);
g_usb_device_close (usb_dev, NULL);
fpi_device_probe_complete (device, serial, NULL, error);
return;
err_close:
g_usb_device_close (usb_dev, NULL);
fpi_device_probe_complete (device, NULL, NULL, error);
}
static void
gx_fp_init (FpDevice *device)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (device);
GError *error = NULL;
int ret = 0;
self->max_stored_prints = FP_MAX_FINGERNUM;
self->is_power_button_shield_on = false;
self->cancellable = g_cancellable_new ();
self->sensorcfg = g_new0 (gxfp_sensor_cfg_t, 1);
ret = gx_proto_init_sensor_config (self->sensorcfg);
if (ret != 0)
{
error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Init sensor failed");
fpi_device_open_complete (FP_DEVICE (self), error);
return;
}
self->sensorcfg->config[6] = self->max_enroll_stage;
if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error))
{
fpi_device_open_complete (FP_DEVICE (self), error);
return;
}
/* Claim usb interface */
if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error))
{
fpi_device_open_complete (FP_DEVICE (self), error);
return;
}
self->task_ssm = fpi_ssm_new (device, fp_init_sm_run_state,
FP_INIT_NUM_STATES);
fpi_ssm_start (self->task_ssm, fp_init_ssm_done);
}
static void
gx_fp_release_interface (FpiDeviceGoodixMoc *self,
GError *error)
{
g_autoptr(GError) release_error = NULL;
g_clear_object (&self->cancellable);
g_clear_pointer (&self->sensorcfg, g_free);
/* Release usb interface */
g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (self)),
0, 0, &release_error);
/* Retain passed error if set, otherwise propagate error from release. */
if (error == NULL)
error = g_steal_pointer (&release_error);
/* Notify close complete */
fpi_device_close_complete (FP_DEVICE (self), error);
}
static void
gx_fp_exit_cb (FpiDeviceGoodixMoc *self,
gxfp_cmd_response_t *resp,
GError *error)
{
if (resp->result >= GX_FAILED)
fp_dbg ("Setting power button shield failed, result: 0x%x", resp->result);
self->is_power_button_shield_on = false;
gx_fp_release_interface (self, error);
}
static void
gx_fp_exit (FpDevice *device)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (device);
if (self->is_power_button_shield_on)
{
goodix_sensor_cmd (self,
MOC_CMD0_PWR_BTN_SHIELD,
MOC_CMD1_PWR_BTN_SHIELD_OFF,
false,
NULL,
0,
gx_fp_exit_cb);
}
else
{
gx_fp_release_interface (self, NULL);
}
}
static void
gx_fp_verify_identify (FpDevice *device)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (device);
self->task_ssm = fpi_ssm_new (device, fp_verify_sm_run_state,
FP_VERIFY_NUM_STATES);
fpi_ssm_start (self->task_ssm, fp_verify_ssm_done);
}
static void
gx_fp_enroll (FpDevice *device)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (device);
self->enroll_stage = 0;
self->is_enroll_identify = true;
self->task_ssm = fpi_ssm_new (device, fp_enroll_sm_run_state,
FP_ENROLL_NUM_STATES);
fpi_ssm_start (self->task_ssm, fp_enroll_ssm_done);
}
static void
gx_fp_template_list (FpDevice *device)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (device);
guint8 dummy[1] = { 0 };
G_DEBUG_HERE ();
goodix_sensor_cmd (self, MOC_CMD0_GETFINGERLIST, MOC_CMD1_DEFAULT,
false,
(const guint8 *) &dummy,
1,
fp_template_list_cb);
}
static void
gx_fp_template_delete (FpDevice *device)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (device);
FpPrint *print = NULL;
g_autoptr(GVariant) data = NULL;
guint8 finger;
const guint8 *user_id;
gsize user_id_len = 0;
const guint8 *tid;
gsize tid_len = 0;
gsize payload_len = 0;
g_autofree guint8 *payload = NULL;
fpi_device_get_delete_data (device, &print);
g_object_get (print, "fpi-data", &data, NULL);
if (!parse_print_data (data, &finger, &tid, &tid_len, &user_id, &user_id_len))
{
fpi_device_delete_complete (device,
fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID));
return;
}
if (!encode_finger_id (tid, tid_len, user_id, user_id_len, &payload, (guint16 *) &payload_len))
{
fpi_device_delete_complete (device,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"encode_finger_id failed"));
return;
}
goodix_sensor_cmd (self, MOC_CMD0_DELETETEMPLATE, MOC_CMD1_DEFAULT,
false,
(const guint8 *) payload,
payload_len,
fp_template_delete_cb);
}
static void
fpi_device_goodixmoc_init (FpiDeviceGoodixMoc *self)
{
}
static void
gx_fp_cancel (FpDevice *device)
{
FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (device);
/* Cancel any current interrupt transfer (resulting us to go into
* response reading mode again); then create a new cancellable
* for the next transfers. */
g_cancellable_cancel (self->cancellable);
g_clear_object (&self->cancellable);
self->cancellable = g_cancellable_new ();
}
static const FpIdEntry id_table[] = {
{ .vid = 0x27c6, .pid = 0x5840, },
{ .vid = 0x27c6, .pid = 0x6496, },
{ .vid = 0x27c6, .pid = 0x60A2, },
{ .vid = 0x27c6, .pid = 0x63AC, },
{ .vid = 0x27c6, .pid = 0x639C, },
{ .vid = 0x27c6, .pid = 0x6594, },
{ .vid = 0, .pid = 0, .driver_data = 0 }, /* terminating entry */
};
static void
fpi_device_goodixmoc_class_init (FpiDeviceGoodixMocClass *klass)
{
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
dev_class->id = "goodixmoc";
dev_class->full_name = "Goodix MOC Fingerprint Sensor";
dev_class->type = FP_DEVICE_TYPE_USB;
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
dev_class->id_table = id_table;
dev_class->nr_enroll_stages = DEFAULT_ENROLL_SAMPLES;
dev_class->open = gx_fp_init;
dev_class->close = gx_fp_exit;
dev_class->probe = gx_fp_probe;
dev_class->enroll = gx_fp_enroll;
dev_class->delete = gx_fp_template_delete;
dev_class->list = gx_fp_template_list;
dev_class->cancel = gx_fp_cancel;
dev_class->verify = gx_fp_verify_identify;
dev_class->identify = gx_fp_verify_identify;
fpi_device_class_auto_initialize_features (dev_class);
dev_class->features |= FP_DEVICE_FEATURE_DUPLICATES_CHECK;
}