Add clear before verify and make state machines callable

This commit is contained in:
mikee512 2025-05-25 14:52:05 -04:00 committed by Xelef2000
parent 20aa186bd5
commit 3d5e73b49d
4 changed files with 589 additions and 148 deletions

View file

@ -35,16 +35,37 @@ struct _FpiDeviceCrfpMoc
GCancellable *interrupt_cancellable;
int fd;
int emul_fd;
int poll_source;
};
G_DEFINE_TYPE (FpiDeviceCrfpMoc, fpi_device_crfpmoc, FP_TYPE_DEVICE)
/* Data for the enroll state machine */
/* State machine private data. All state machines get this and it is
* cleared/freed at the completion of the state machine.
*/
struct crfpmoc_ssm_data
{
FpiSsm *ssm;
FpiDeviceCrfpMoc *self;
int timeout;
int timeout_state;
int poll;
};
/* Data for the enroll state machine
*/
struct crfpmoc_enroll_data
{
FpPrint *print; /* print template to be completed and returned */
int stage; /* stage of enrollment */
struct crfpmoc_ssm_data parent;
FpPrint *print; /* print template to be completed and returned */
int stage; /* stage of enrollment */
};
/* Data for the verify state machine
*/
struct crfpmoc_verify_data
{
struct crfpmoc_ssm_data parent;
FpPrint *match; /* print matched */
};
static const FpIdEntry crfpmoc_id_table[] = {
@ -53,12 +74,69 @@ static const FpIdEntry crfpmoc_id_table[] = {
};
/* Define the variant data format and offsets for FpPrint */
#define CRFPMOC_PRINT_DATA_VARIANT_FMT "(@ay@ayii)"
#define CRFPMOC_PRINT_DATA_VARIANT_FMT "(@ay@ayqq)"
#define CRFPMOC_PRINT_DATA_VARIANT_PRINT_ID 0
#define CRFPMOC_PRINT_DATA_VARIANT_TEMPLATE 1
#define CRFPMOC_PRINT_DATA_VARIANT_MAX_OUTSIZE 2
#define CRFPMOC_PRINT_DATA_VARIANT_MAX_TEMPLATES 3
/* Clear and free the state machine private data
*/
static void
crfpmoc_ssm_data_free (void *arg)
{
struct crfpmoc_ssm_data *data = (struct crfpmoc_ssm_data *) arg;
/* Cancel any poll source */
if (data->poll)
{
fp_dbg ("crfpmoc_ssm_data_free: remove poll source %d", data->poll);
g_source_remove (data->poll);
}
/* Cancel any timeout source */
if (data->timeout)
{
fp_dbg ("crfpmoc_ssm_data_free: remove timeout source %d", data->timeout);
g_source_remove (data->timeout);
}
g_free (data);
}
/* Wrapper to start a sub-state machine after adding the private data. The data
* can be supplied and is required to be crfpmoc_ssm_data or contain it as its
* first member.
*/
static void
crfpmoc_ssm_start_subsm (FpiSsm *parent, FpiSsm *ssm, FpiDeviceCrfpMoc *self,
struct crfpmoc_ssm_data *data)
{
if (data == NULL)
data = g_malloc0 (sizeof (*data));
data->self = self;
data->ssm = ssm;
fpi_ssm_set_data (ssm, data, crfpmoc_ssm_data_free);
fpi_ssm_start_subsm (parent, ssm);
}
/* Wrapper to start a state machine after adding the private data. The data
* can be supplied and is required to be crfpmoc_ssm_data or contain it as its
* first member.
*/
static void
crfpmoc_ssm_start (FpiSsm *ssm, FpiDeviceCrfpMoc *self,
struct crfpmoc_ssm_data *data,
FpiSsmCompletedCallback cb)
{
if (data == NULL)
data = g_malloc0 (sizeof (*data));
data->self = self;
data->ssm = ssm;
fpi_ssm_set_data (ssm, data, crfpmoc_ssm_data_free);
fpi_ssm_start (ssm, cb);
}
static const gchar *const crfpmoc_meanings[] = {
"SUCCESS",
"INVALID_COMMAND",
@ -222,7 +300,7 @@ crfpmoc_get_print_data (FpPrint *print, guint8 **template,
CRFPMOC_PRINT_DATA_VARIANT_MAX_OUTSIZE);
if (variant_ec_max)
{
*ec_max_outsize = g_variant_get_int32 (variant_ec_max);
*ec_max_outsize = g_variant_get_uint16 (variant_ec_max);
fp_dbg ("Extracted ec_max_outsize: %d", *ec_max_outsize);
}
else
@ -238,7 +316,7 @@ crfpmoc_get_print_data (FpPrint *print, guint8 **template,
CRFPMOC_PRINT_DATA_VARIANT_MAX_TEMPLATES);
if (variant_max_templates)
{
*max_templates = g_variant_get_int32 (variant_max_templates);
*max_templates = g_variant_get_uint16 (variant_max_templates);
fp_dbg ("Extracted max_templates: %d", *max_templates);
}
else
@ -267,6 +345,11 @@ crfpmoc_get_print_data (FpPrint *print, guint8 **template,
}
}
/* Self test uses umockdev for replay of a captured sequence. At this
* time umockdev supports cros_ec only for replay. We need to create
* the sequence. Record all the ioctls for replay in the format
* umockdev requires.
*/
static void
crfpmoc_umockdev_record (FpiDeviceCrfpMoc *self, int res,
int cmd, void *arg)
@ -289,11 +372,29 @@ crfpmoc_umockdev_record (FpiDeviceCrfpMoc *self, int res,
len = snprintf (buffer, bufsiz, "CROS_EC_DEV_IOCXCMD_V2 %u ",
s_cmd->insize);
g_assert (len < 48);
while (size > 0)
/* Send the command */
while (size > s_cmd->insize)
{
len += snprintf (&buffer[len], bufsiz - len, "%02X", *ptr++);
size--;
}
/* Send the data. If this is a frame suppress the actual data for privacy */
if (s_cmd->command == CRFPMOC_EC_CMD_FP_FRAME)
{
memset (&buffer[len], '0', size * 2);
len += size * 2;
size = 0;
}
else
{
while (size > 0)
{
len += snprintf (&buffer[len], bufsiz - len, "%02X", *ptr++);
size--;
}
}
g_assert (len < bufsiz);
buffer[len++] = '\n';
if (write (self->emul_fd, buffer, len) != len)
@ -363,19 +464,35 @@ crfpmoc_ec_command (FpiDeviceCrfpMoc *self, int command,
return r;
}
/* Completion callback for event reporting. Get the event and decode
* for now. The function may be used with timeout support and if so
* the timeout needs to be cancelled here. The state machine that
* requested the poll is passed as an argument. Use the private data
* to see if a timeout is active.
*/
static gboolean
crfpmoc_read_bytes (gint fd, GIOCondition condition,
gpointer user_data)
{
FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (user_data);
// FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (user_data);
FpiSsm *ssm = (FpiSsm *) user_data;
struct crfpmoc_ssm_data *data = fpi_ssm_get_data (ssm);
int rv;
gint event;
gboolean has_more;
struct crfpmoc_ec_response_get_next_event_v1 buffer = {0};
fp_dbg ("crfpmoc_read_bytes: called");
if (fd != self->fd)
goto one_shot_out;
// if (fd != self->fd)
// goto one_shot_out;
/* If there is an active timeout on this state machine remove it */
if (data->timeout)
{
fp_dbg ("crfpmoc_read_bytes: remove timeout source %d", data->timeout);
g_source_remove (data->timeout);
data->timeout = 0;
}
rv = read (fd, &buffer, sizeof (buffer));
fp_dbg ("crfpmoc_read_bytes: read %d bytes", rv);
@ -383,14 +500,14 @@ crfpmoc_read_bytes (gint fd, GIOCondition condition,
if (rv == 0)
{
fp_warn ("Timeout waiting for MKBP event");
fpi_ssm_mark_failed (self->task_ssm,
fpi_ssm_mark_failed (ssm,
fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
goto one_shot_out;
}
else if (rv < 0)
{
fp_warn ("Error polling for MKBP event");
fpi_ssm_mark_failed (self->task_ssm,
fpi_ssm_mark_failed (ssm,
fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
goto one_shot_out;
}
@ -404,6 +521,9 @@ crfpmoc_read_bytes (gint fd, GIOCondition condition,
{
case CRFPMOC_EC_MKBP_EVENT_FINGERPRINT:
fp_dbg ("crfpmoc_read_bytes: fingerprint 0x%08x", buffer.data.fp_events);
/* Enroll mode
*/
if (buffer.data.fp_events & CRFPMOC_EC_MKBP_FP_ENROLL)
{
guint error = CRFPMOC_EC_MKBP_FP_ERRCODE (buffer.data.fp_events);
@ -431,6 +551,9 @@ crfpmoc_read_bytes (gint fd, GIOCondition condition,
}
fp_dbg (" status: %d (%s)", error, reason ? reason : "");
}
/* Match mode
*/
if (buffer.data.fp_events & CRFPMOC_EC_MKBP_FP_MATCH)
{
guint error = CRFPMOC_EC_MKBP_FP_ERRCODE (buffer.data.fp_events);
@ -478,33 +601,47 @@ crfpmoc_read_bytes (gint fd, GIOCondition condition,
}
fp_dbg (" status: %d (%s)", error, reason ? reason : "");
}
/* Finger down mode
*/
if (buffer.data.fp_events & CRFPMOC_EC_MKBP_FP_FINGER_DOWN)
fp_dbg (" Finger down");
/* Finger up mode
*/
if (buffer.data.fp_events & CRFPMOC_EC_MKBP_FP_FINGER_UP)
fp_dbg (" Finger up");
/* ??
*/
if (buffer.data.fp_events & CRFPMOC_EC_MKBP_FP_IMAGE_READY)
fp_dbg (" Image ready");
break;
}
fpi_ssm_next_state (self->task_ssm);
/* Make sure we do not run in the callback context */
fpi_ssm_next_state_delayed (ssm, 1);
one_shot_out:
/* The call of this function with FALSE return will complete
* the poll source. No need to remove it on close or cancel.
*/
self->poll_source = 0;
data->poll = 0;
fp_dbg ("crfpmoc_read_bytes: return FALSE");
return FALSE;
#ifdef LATER
rearm_out:
/* Keep the poll source active */
fp_dbg ("crfpmoc_read_bytes: return TRUE");
return TRUE;
#endif
}
static void
crfpmoc_ec_pollevent (FpiDeviceCrfpMoc *self, unsigned long mask)
crfpmoc_ec_pollevent (FpiSsm *ssm, unsigned long mask)
{
struct crfpmoc_ssm_data *data = fpi_ssm_get_data (ssm);
FpiDeviceCrfpMoc *self = data->self;
int rv;
rv = ioctl (self->fd, CRFPMOC_CROS_EC_DEV_IOCEVENTMASK_V2, mask);
@ -515,8 +652,8 @@ crfpmoc_ec_pollevent (FpiDeviceCrfpMoc *self, unsigned long mask)
return;
}
self->poll_source = g_unix_fd_add (self->fd, G_IO_IN, crfpmoc_read_bytes, self);
fp_dbg ("crfpmoc_ec_pollevent: created poll source %d", self->poll_source);
data->poll = g_unix_fd_add (self->fd, G_IO_IN, crfpmoc_read_bytes, ssm);
fp_dbg ("crfpmoc_ec_pollevent: created poll source %d", data->poll);
}
static gboolean
@ -569,6 +706,8 @@ crfpmoc_cmd_fp_seed (FpiDeviceCrfpMoc *self, const char *seed,
return TRUE;
}
// TODO: should this be converted to a sub state machine instead of
// the sync completion ?
static gboolean
crfpmoc_fp_set_context (FpiDeviceCrfpMoc *self,
const char *context, GError **error)
@ -795,9 +934,28 @@ crfmoc_cmd_fp_ensure_seed (FpiDeviceCrfpMoc *self,
return TRUE;
}
static void
crfpmoc_show_proto_info (struct crfpmoc_ec_response_protocol_get_info *protocol_info)
{
gsize len = 0;
gsize buflen = 256;
g_autofree gchar *buffer = g_malloc (buflen);
for (int i = 0; i < 32; i++)
if (protocol_info->protocol_versions & (1ULL << i))
len += snprintf (&buffer[len], buflen - len, "%s%d", len == 0 ? "" : ",", i);
buffer[buflen - 1] = '\0';
fp_dbg ("crfpmoc_ec_max_outsize: vers %s, max_out %d, max_in %d, flags 0x%08x",
buffer,
protocol_info->max_request_packet_size,
protocol_info->max_response_packet_size,
protocol_info->flags);
}
static gboolean
crfpmoc_ec_max_insize (FpiDeviceCrfpMoc *self,
guint32 *max_insize, GError **error)
guint16 *max_insize, GError **error)
{
struct crfpmoc_ec_response_protocol_get_info protocol_info;
gboolean rv;
@ -807,6 +965,7 @@ crfpmoc_ec_max_insize (FpiDeviceCrfpMoc *self,
if (rv != EC_RES_SUCCESS)
return FALSE;
crfpmoc_show_proto_info (&protocol_info);
*max_insize = protocol_info.max_response_packet_size -
sizeof (struct crfpmoc_ec_host_response);
@ -825,6 +984,7 @@ crfpmoc_ec_max_outsize (FpiDeviceCrfpMoc *self,
if (rv != EC_RES_SUCCESS)
return FALSE;
crfpmoc_show_proto_info (&protocol_info);
*max_outsize = protocol_info.max_request_packet_size -
sizeof (struct crfpmoc_ec_host_request);
@ -869,7 +1029,7 @@ crfpmoc_fp_download_template (FpiDeviceCrfpMoc *self,
gsize rsize = sizeof (*info);
const gint max_attempts = 3;
gint num_attempts;
guint32 ec_max_insize;
guint16 ec_max_insize;
gint template_idx = index + CRFPMOC_FP_FRAME_INDEX_TEMPLATE;
guint16 sum = 0;
@ -1003,13 +1163,15 @@ crfpmoc_fp_upload_template (FpiDeviceCrfpMoc *self,
}
static void
crfpmoc_cmd_wait_event_fingerprint (FpiDeviceCrfpMoc *self)
crfpmoc_cmd_wait_event_fingerprint (FpiSsm *ssm)
{
long event_type = CRFPMOC_EC_MKBP_EVENT_FINGERPRINT;
crfpmoc_ec_pollevent (self, 1 << event_type);
crfpmoc_ec_pollevent (ssm, 1 << event_type);
}
/* Generic task completion callback to report any errors
*/
static void
crfpmoc_task_ssm_done (FpiSsm *ssm, FpDevice *device,
GError *error)
@ -1042,6 +1204,7 @@ crfpmoc_open (FpDevice *device)
{
g_set_error (&err, G_IO_ERROR, g_io_error_from_errno (errno),
"unable to open misc device");
fp_dbg ("crfpmoc_open: open_complete");
fpi_device_open_complete (device, err);
return;
}
@ -1054,6 +1217,17 @@ crfpmoc_open (FpDevice *device)
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
self->emul_fd = creat ("/tmp/crfpmoc.ioctl", 0777);
/* Just for debug messages */
guint16 ec_max_outsize;
if (!crfpmoc_ec_max_outsize (self, &ec_max_outsize, &err))
fp_err ("Failed to get max outsize");
/* Just for debug messages */
guint16 ec_max_insize;
if (!crfpmoc_ec_max_insize (self, &ec_max_insize, &err))
fp_err ("Failed to get max insize");
fp_dbg ("crfpmoc_open: open_complete");
fpi_device_open_complete (device, NULL);
}
@ -1079,14 +1253,6 @@ crfpmoc_cancel (FpDevice *device)
fp_dbg ("Cancel");
FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (device);
/* Cancel any poll source */
if (self->poll_source)
{
fp_dbg ("crfpmoc_cancel: remove poll source %d", self->poll_source);
g_source_remove (self->poll_source);
self->poll_source = 0;
}
/* Cancel the running state machine, if any */
if (self->task_ssm != NULL)
fpi_ssm_mark_failed (self->task_ssm,
@ -1107,6 +1273,7 @@ crfpmoc_suspend (FpDevice *device)
crfpmoc_cancel (device);
g_cancellable_cancel (fpi_device_get_cancellable (device));
fp_dbg ("crfpmoc_suspend: suspend_complete");
fpi_device_suspend_complete (device, NULL);
}
@ -1129,6 +1296,7 @@ crfpmoc_close (FpDevice *device)
close (self->emul_fd);
self->emul_fd = -1;
}
fp_dbg ("crfpmoc_close: close_complete");
fpi_device_close_complete (device, NULL);
}
@ -1152,8 +1320,68 @@ static void
handle_enroll_wait_enroll_complete (FpDevice *device,
FpiDeviceCrfpMoc *self)
{
fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED);
crfpmoc_cmd_wait_event_fingerprint (self);
/* Sensor awaits the finger down
*/
fpi_device_report_finger_status_changes (device,
FP_FINGER_STATUS_NEEDED,
FP_FINGER_STATUS_NONE);
crfpmoc_cmd_wait_event_fingerprint (self->task_ssm);
}
static gint
crfpmoc_ssm_timeout (gpointer user_data)
{
struct crfpmoc_ssm_data *data = (struct crfpmoc_ssm_data *) user_data;
fp_dbg ("crfpmoc_ssm_timeout: timeout %d, move to state %d",
data->timeout, data->timeout_state);
/* Timeout should be cancelled, make sure cleanup does not see it */
data->timeout = 0;
/* If there is an active poller on this state machine remove it */
if (data->poll)
{
fp_dbg ("crfpmoc_ssm_timeout: remove poll source %d", data->poll);
g_source_remove (data->poll);
data->poll = 0;
}
/* Can't call the state machine from here, make sure we exit first */
fpi_ssm_jump_to_state_delayed (data->ssm, data->timeout_state, 1);
return FALSE;
}
static void
handle_xxx_wait_finger_up (FpDevice *device,
FpiDeviceCrfpMoc *self,
FpiSsm *ssm,
int state,
int timeout)
{
GError *error = NULL;
guint32 mode;
struct crfpmoc_ssm_data *data = fpi_ssm_get_data (ssm);
data->timeout_state = state;
data->timeout = g_timeout_add (timeout, crfpmoc_ssm_timeout, data);
fp_dbg ("handle_xxx_wait_finger_up: start, added timeout %d", data->timeout);
/* Tell the upper layers the used should remove finger. This does
* not currently seem to do much. To actually get the upper layers
* to prompt the user it seems a retry error needs to be returned
* in a report.
*/
fpi_device_report_finger_status_changes (device,
FP_FINGER_STATUS_NONE,
FP_FINGER_STATUS_NEEDED);
if (!crfpmoc_cmd_fp_mode (self, CRFPMOC_FP_MODE_FINGER_UP, &mode, &error))
fpi_ssm_mark_failed (self->task_ssm, error);
crfpmoc_cmd_wait_event_fingerprint (ssm);
}
static void
@ -1180,7 +1408,9 @@ handle_enroll_sensor_check (FpiSsm *ssm, FpDevice *device,
}
else
{
fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT);
fpi_device_report_finger_status_changes (device,
FP_FINGER_STATUS_PRESENT,
FP_FINGER_STATUS_NONE);
enroll_data->stage++;
fp_info ("Partial capture successful (%d/%d).", enroll_data->stage,
@ -1193,13 +1423,17 @@ handle_enroll_sensor_check (FpiSsm *ssm, FpDevice *device,
}
else if (mode == 0)
{
fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT);
fpi_device_report_finger_status_changes (device,
FP_FINGER_STATUS_PRESENT,
FP_FINGER_STATUS_NONE);
fpi_ssm_next_state (ssm);
}
else
{
fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT);
fpi_device_report_finger_status_changes (device,
FP_FINGER_STATUS_PRESENT,
FP_FINGER_STATUS_NONE);
fpi_device_enroll_progress (device, enroll_data->stage, NULL,
fpi_device_retry_new_msg (FP_DEVICE_RETRY_GENERAL,
@ -1263,7 +1497,7 @@ handle_enroll_commit (FpiSsm *ssm, FpDevice *device,
info.template_max);
}
fp_info ("Enrollment was successful!");
fp_info ("handle_enroll_commit: enroll_complete");
fpi_device_enroll_complete (device, g_object_ref (enroll_data->print), NULL);
fpi_ssm_mark_completed (ssm);
@ -1274,25 +1508,40 @@ crfpmoc_enroll_run_state (FpiSsm *ssm, FpDevice *device)
{
FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (device);
struct crfpmoc_enroll_data *enroll_data = fpi_ssm_get_data (ssm);
int state = fpi_ssm_get_cur_state (ssm);
switch (fpi_ssm_get_cur_state (ssm))
switch (state)
{
case ENROLL_SENSOR_ENROLL:
case ENROLL_SENSOR_ENROLL: /* State 0 */
handle_enroll_sensor_enroll (ssm, self);
break;
case ENROLL_WAIT_ENROLL_COMPLETE:
case ENROLL_WAIT_ENROLL_COMPLETE: /* State 1 */
handle_enroll_wait_enroll_complete (device, self);
break;
case ENROLL_SENSOR_CHECK:
#if 0
/* During self test recording following an enroll with a verify
* without lifting the finger seems to randomly fail. This
* solves the issue but leaves the default enroll screen waiting
* for the finger up with no user prompt. It will only complete
* when the finger is removed. Seems to be a poor user interaction.
*/
case ENROLL_WAIT_FINGER_UP: /* State 2 */
handle_xxx_wait_finger_up (device, self, ssm);
break;
#endif
case ENROLL_SENSOR_CHECK: /* State 3 */
handle_enroll_sensor_check (ssm, device, self, enroll_data);
break;
case ENROLL_COMMIT:
case ENROLL_COMMIT: /* State 4 */
handle_enroll_commit (ssm, device, self, enroll_data);
break;
}
fp_dbg ("crfpmoc_enroll_run_state: state %d return",
state);
}
static void
@ -1306,9 +1555,9 @@ crfpmoc_enroll (FpDevice *device)
g_malloc0 (sizeof (struct crfpmoc_enroll_data));
r = crfpmoc_set_keys (self, &error);
if (!r)
{
fp_dbg ("crfpmoc_enroll: enroll_complete");
fpi_device_enroll_complete (device, NULL, error);
return;
}
@ -1319,8 +1568,9 @@ crfpmoc_enroll (FpDevice *device)
g_assert (self->task_ssm == NULL);
self->task_ssm =
fpi_ssm_new (device, crfpmoc_enroll_run_state, ENROLL_STATES);
fpi_ssm_set_data (self->task_ssm, enroll_data, g_free);
fpi_ssm_start (self->task_ssm, crfpmoc_task_ssm_done);
crfpmoc_ssm_start (self->task_ssm, self,
(struct crfpmoc_ssm_data *) enroll_data,
crfpmoc_task_ssm_done);
}
static void
@ -1439,8 +1689,13 @@ static void
handle_verify_wait_match_complete (FpDevice *device,
FpiDeviceCrfpMoc *self)
{
fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED);
crfpmoc_cmd_wait_event_fingerprint (self);
/* Sensor awaits the finger down
*/
fpi_device_report_finger_status_changes (device,
FP_FINGER_STATUS_NEEDED,
FP_FINGER_STATUS_NONE);
crfpmoc_cmd_wait_event_fingerprint (self->task_ssm);
}
static void
@ -1464,12 +1719,16 @@ handle_verify_sensor_check (FpiSsm *ssm, FpDevice *device,
}
else if (mode == 0)
{
fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT);
fpi_device_report_finger_status_changes (device,
FP_FINGER_STATUS_PRESENT,
FP_FINGER_STATUS_NONE);
fpi_ssm_next_state (ssm);
}
else
{
fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT);
fpi_device_report_finger_status_changes (device,
FP_FINGER_STATUS_PRESENT,
FP_FINGER_STATUS_NONE);
fpi_ssm_mark_failed (ssm,
fpi_device_retry_new_msg (FP_DEVICE_RETRY_GENERAL,
"FP mode: (0x%x)", mode));
@ -1477,6 +1736,47 @@ handle_verify_sensor_check (FpiSsm *ssm, FpDevice *device,
}
}
/* Verify completion
* report (only retry errors can be reported)
* FPI_MATCH_SUCCESS
* error must be NULL
* print same as suppplied
* FPI_MATCH_FAIL
* error must be NULL
* print must be NULL
* FPI_MATCH_ERROR (retry)
* error must be valid
* print must be NULL
* complete
* FPI_MATCH_SUCCESS
* FPI_MATCH_FAIL
* error must be NULL
* FPI_MATCH_ERROR
* error must be same as report for retry, otherwise no report
* and non retry error
*
* Identify completion
* report (only retry errors can be reported)
* Successful match
* match from gallery that matched
* print from gallery that matched (we have no storage so cant
* match anything else)
* error must be NULL
* No match
* match must be NULL
* print must be NULL (we have no ability to return a print without enroll)
* error must be NULL
* Error
* match must be NULL
* print must be NULL (we have no ability to return a print without enroll)
* complete
* Successful match
* No match
* error must be NULL
* Error
* error must be same as report for retry, otherwise no report
* and non retry error
*/
static void
handle_verify_check (FpiSsm *ssm, FpDevice *device,
FpiDeviceCrfpMoc *self)
@ -1529,6 +1829,8 @@ handle_verify_check (FpiSsm *ssm, FpDevice *device,
}
else
{
// This should not occur as we can only match prints
// from the gallery
fp_warn ("Template index %d is out of range", template_idx);
fpi_device_identify_report (device, NULL, NULL, NULL);
}
@ -1541,9 +1843,15 @@ handle_verify_check (FpiSsm *ssm, FpDevice *device,
}
if (is_identify)
fpi_device_identify_complete (device, NULL);
{
fp_dbg ("handle_verify_check: identify_complete");
fpi_device_identify_complete (device, NULL);
}
else
fpi_device_verify_complete (device, NULL);
{
fp_dbg ("handle_verify_check: verify_complete");
fpi_device_verify_complete (device, NULL);
}
fpi_ssm_mark_completed (ssm);
}
@ -1564,36 +1872,42 @@ crfpmoc_cmd_wait_for_it (FpiSsm *ssm, FpDevice *device)
}
static void
crfpmoc_verify_run_state (FpiSsm *ssm, FpDevice *device)
crfpmoc_finger_up_run_state (FpiSsm *ssm, FpDevice *device)
{
FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (device);
int state = fpi_ssm_get_cur_state (ssm);
switch (fpi_ssm_get_cur_state (ssm))
switch (state)
{
case VERIFY_UPLOAD_TEMPLATE:
handle_verify_upload_template (ssm, device, self);
case FINGER_UP_START: /* State 0 */
handle_xxx_wait_finger_up (device, self, ssm,
FINGER_UP_TIMEOUT,
5000);
break;
case VERIFY_SENSOR_MATCH:
handle_verify_sensor_match (ssm, self);
case FINGER_UP_DONE: /* State 1 */
fpi_ssm_mark_completed (ssm);
break;
case VERIFY_WAIT_MATCH_COMPLETE:
handle_verify_wait_match_complete (device, self);
break;
case VERIFY_SENSOR_CHECK:
handle_verify_sensor_check (ssm, device, self);
break;
case VERIFY_CHECK:
handle_verify_check (ssm, device, self);
break;
default:
fpi_ssm_next_state (ssm);
/* User has not lifted finger. Send a retry error to prompt them
*/
case FINGER_UP_TIMEOUT: /* State 2 */
fpi_ssm_mark_failed (ssm,
fpi_device_retry_new (FP_DEVICE_RETRY_REMOVE_FINGER));
break;
}
fp_dbg ("crfpmoc_finger_up_run_state: state %d return",
state);
}
static void
handle_verify_finger_up (FpiSsm *ssm, FpDevice *device,
FpiDeviceCrfpMoc *self)
{
FpiSsm *sub = fpi_ssm_new (device, crfpmoc_finger_up_run_state,
FINGER_UP_STATES);
crfpmoc_ssm_start_subsm (self->task_ssm, sub, self, NULL);
}
static void
@ -1603,8 +1917,9 @@ crfpmoc_clear_storage_run_state (FpiSsm *ssm, FpDevice *device)
gboolean r;
guint32 mode;
GError *error = NULL;
int state = fpi_ssm_get_cur_state (ssm);
switch (fpi_ssm_get_cur_state (ssm))
switch (state)
{
case CLEAR_STORAGE_SENSOR_RESET:
fp_dbg ("crfpmoc_clear_storage_run_state: reset");
@ -1631,31 +1946,134 @@ crfpmoc_clear_storage_run_state (FpiSsm *ssm, FpDevice *device)
case CLEAR_STORAGE_SENSOR_DONE:
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_CLEAR_STORAGE)
fpi_device_clear_storage_complete (device, NULL);
{
fp_dbg ("crfpmoc_clear_storage_run_state: clear_storage_complete");
fpi_device_clear_storage_complete (device, NULL);
}
fpi_ssm_mark_completed (ssm);
break;
}
fp_dbg ("crfpmoc_clear_storage_run_state: state %d return",
state);
}
static void
crfpmoc_ident_clear_done (FpiSsm *ssm, FpDevice *device,
GError *error)
handle_verify_clear_storage (FpiSsm *ssm, FpDevice *device,
FpiDeviceCrfpMoc *self)
{
FpiSsm *sub = fpi_ssm_new (device, crfpmoc_clear_storage_run_state,
CLEAR_STORAGE_STATES);
crfpmoc_ssm_start_subsm (self->task_ssm, sub, self, NULL);
}
static void
handle_verify_set_seed (FpiSsm *ssm, FpDevice *device,
FpiDeviceCrfpMoc *self)
{
GError *error = NULL;
if (!crfpmoc_set_keys (self, &error))
fpi_ssm_mark_failed (ssm, error);
else
fpi_ssm_next_state (ssm);
}
static void
crfpmoc_verify_run_state (FpiSsm *ssm, FpDevice *device)
{
FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (device);
gboolean r;
int state = fpi_ssm_get_cur_state (ssm);
crfpmoc_task_ssm_done (ssm, device, error);
r = crfpmoc_set_keys (self, &error);
if (!r)
switch (state)
{
fpi_device_identify_complete (device, error);
return;
}
/* Make sure no residual templates are left in the sensor
*/
case VERIFY_CLEAR_STORAGE:
handle_verify_clear_storage (ssm, device, self);
break;
g_assert (self->task_ssm == NULL);
self->task_ssm = fpi_ssm_new (device, crfpmoc_verify_run_state, VERIFY_STATES);
fpi_ssm_start (self->task_ssm, crfpmoc_task_ssm_done);
/* Make sure the finger is actually up
*/
case VERIFY_FINGER_UP:
handle_verify_finger_up (ssm, device, self);
break;
/* Set the seed and the context
*/
case VERIFY_SET_SEED:
handle_verify_set_seed (ssm, device, self);
break;
/* Upload the supplied template(s) for match
*/
case VERIFY_UPLOAD_TEMPLATE:
handle_verify_upload_template (ssm, device, self);
break;
/* Switch to match mode
*/
case VERIFY_SENSOR_MATCH:
handle_verify_sensor_match (ssm, self);
break;
/* Wait for the match to report completion event
*/
case VERIFY_WAIT_MATCH_COMPLETE:
handle_verify_wait_match_complete (device, self);
break;
/* Wait for the match mode to indicate complete
*/
case VERIFY_SENSOR_CHECK:
handle_verify_sensor_check (ssm, device, self);
break;
/* Get the match status and report it
*/
case VERIFY_CHECK:
handle_verify_check (ssm, device, self);
break;
}
fp_dbg ("crfpmoc_verify_run_state: state %d return",
state);
}
static void
crfpmoc_verify_ssm_done (FpiSsm *ssm, FpDevice *device,
GError *error)
{
fp_dbg ("crfpmoc_verify_ssm_done");
FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (device);
// g_assert (!self->task_ssm || self->task_ssm == ssm);
self->task_ssm = NULL;
/* If the state machine failed the report and completion was not
* sent. Send it here.
*/
if (error)
{
if (error->domain == FP_DEVICE_RETRY)
{
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY)
fpi_device_verify_report (device, FPI_MATCH_ERROR, NULL, g_steal_pointer (&error));
else
fpi_device_identify_report (device, NULL, NULL, g_steal_pointer (&error));
}
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY)
{
fp_dbg ("crfpmoc_verify_ssm_done: verify_complete");
fpi_device_verify_complete (device, error);
}
else
{
fp_dbg ("crfpmoc_verify_ssm_done: identify_complete");
fpi_device_identify_complete (device, error);
}
}
crfpmoc_cancel (device);
}
static void
@ -1666,9 +2084,8 @@ crfpmoc_identify_verify (FpDevice *device)
fp_dbg ("Identify or Verify");
g_assert (self->task_ssm == NULL);
self->task_ssm = fpi_ssm_new (device, crfpmoc_clear_storage_run_state,
CLEAR_STORAGE_STATES);
fpi_ssm_start (self->task_ssm, crfpmoc_ident_clear_done);
self->task_ssm = fpi_ssm_new (device, crfpmoc_verify_run_state, VERIFY_STATES);
crfpmoc_ssm_start (self->task_ssm, self, NULL, crfpmoc_verify_ssm_done);
}
static void
@ -1680,7 +2097,7 @@ crfpmoc_clear_storage (FpDevice *device)
g_assert (self->task_ssm == NULL);
self->task_ssm = fpi_ssm_new (device, crfpmoc_clear_storage_run_state,
CLEAR_STORAGE_STATES);
fpi_ssm_start (self->task_ssm, crfpmoc_task_ssm_done);
crfpmoc_ssm_start (self->task_ssm, self, NULL, crfpmoc_task_ssm_done);
}
static void
@ -1688,7 +2105,6 @@ fpi_device_crfpmoc_init (FpiDeviceCrfpMoc *self)
{
self->fd = -1;
self->emul_fd = -1;
self->poll_source = 0;
}
static void

View file

@ -448,26 +448,45 @@ enum crfpmoc_ec_status {
/* SSM task states and various status enums */
typedef enum {
ENROLL_SENSOR_ENROLL,
ENROLL_WAIT_ENROLL_COMPLETE,
ENROLL_SENSOR_CHECK,
ENROLL_COMMIT,
/* Enroll state machine states
*/
enum {
ENROLL_SENSOR_ENROLL, /* Enter enroll mode */
ENROLL_WAIT_ENROLL_COMPLETE, /* Wait for command completion event */
ENROLL_SENSOR_CHECK, /* Verify mode has indicated completion */
// ENROLL_WAIT_FINGER_UP, /* Wait for finger to be removed */
ENROLL_COMMIT, /* Download template and return print */
ENROLL_STATES,
} EnrollStates;
};
typedef enum {
VERIFY_UPLOAD_TEMPLATE,
VERIFY_SENSOR_MATCH,
VERIFY_WAIT_MATCH_COMPLETE,
VERIFY_SENSOR_CHECK,
VERIFY_CHECK,
/* Verify/Identify state machine states
*/
enum {
VERIFY_CLEAR_STORAGE, /* Clear all stored prints */
VERIFY_FINGER_UP, /* Make sure the finger is not on the sensor */
VERIFY_SET_SEED, /* Setup the seed and context */
VERIFY_UPLOAD_TEMPLATE, /* Upload all gallery templates */
VERIFY_SENSOR_MATCH, /* Enter match mode */
VERIFY_WAIT_MATCH_COMPLETE, /* Wait for match complete event */
VERIFY_SENSOR_CHECK, /* Verify mode has indicated completion */
VERIFY_CHECK, /* Get the match completion status */
VERIFY_STATES,
} VerifyStates;
};
typedef enum {
CLEAR_STORAGE_SENSOR_RESET,
CLEAR_STORAGE_SENSOR_WAIT,
CLEAR_STORAGE_SENSOR_DONE,
/* Clear storage state machine states
*/
enum {
CLEAR_STORAGE_SENSOR_RESET, /* Enter reset sensor mode */
CLEAR_STORAGE_SENSOR_WAIT, /* Wait for mode to indicate completion */
CLEAR_STORAGE_SENSOR_DONE, /* Return results */
CLEAR_STORAGE_STATES,
} ClearStorageStates;
};
/* Clear storage state machine states
*/
enum {
FINGER_UP_START,
FINGER_UP_DONE,
FINGER_UP_TIMEOUT,
FINGER_UP_STATES,
};

View file

@ -1,4 +1,6 @@
@DEV /dev/cros_fp
CROS_EC_DEV_IOCXCMD_V2 12 000000000B000000000000000C00000000000000080000002002200201000000
CROS_EC_DEV_IOCXCMD_V2 12 000000000B000000000000000C00000000000000080000002002200201000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000
@ -48,6 +50,7 @@ CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000004000000
CROS_EC_DEV_IOCXCMD_V2 8 00000000090400000000000008000000000000000100000001000000
CROS_EC_DEV_IOCXCMD_V2 0 0100000006040000240000000000000000000000
CROS_EC_DEV_IOCXCMD_V2 0 0100000006040000240000000000000010000000
@ -77,12 +80,14 @@ CROS_EC_DEV_IOCXCMD_V2 0 00000000050400007C0000000000000000000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000040000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000
CROS_EC_DEV_IOCXCMD_V2 48 010000000304000000000000300000000000000046504320090000001F020000010000009466000047524559A000A0000800FF0324140000050001000100000004000000
CROS_EC_DEV_IOCXCMD_V2 22 00000000070400000000000016000000000000002471010024E802005C5E04003CD54A503A0000000000
CROS_EC_DEV_IOCXCMD_V2 22 00000000070400000000000016000000000000002471010098C40200D03A0400E81B920D4D0000000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000004000000
CROS_EC_DEV_IOCXCMD_V2 8 00000000090400000000000008000000000000000100000001000000
CROS_EC_DEV_IOCXCMD_V2 0 0100000006040000240000000000000000000000
CROS_EC_DEV_IOCXCMD_V2 0 0100000006040000240000000000000010000000
@ -112,7 +117,8 @@ CROS_EC_DEV_IOCXCMD_V2 0 00000000050400007C0000000000000000000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000040000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000
CROS_EC_DEV_IOCXCMD_V2 48 010000000304000000000000300000000000000046504320090000001F020000010000009466000047524559A000A0000800FF0324140000050001000100000004000000
CROS_EC_DEV_IOCXCMD_V2 22 000000000704000000000000160000000000000024710100E0EA020018610400342F58503A0000000000
CROS_EC_DEV_IOCXCMD_V2 22 00000000070400000000000016000000000000002471010078C10200783804007C52A90D4D0000000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000
CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000

View file

@ -52,39 +52,39 @@ A: power/runtime_usage=0\n
A: power/wakeup=enabled\n
A: power/wakeup_abort_count=0\n
A: power/wakeup_active=0\n
A: power/wakeup_active_count=856\n
A: power/wakeup_active_count=1742\n
A: power/wakeup_count=0\n
A: power/wakeup_expire_count=0\n
A: power/wakeup_last_time_ms=247138441\n
A: power/wakeup_last_time_ms=288328363\n
A: power/wakeup_max_time_ms=0\n
A: power/wakeup_total_time_ms=0\n
A: statistics/bytes=5478464\n
A: statistics/bytes_rx=5478464\n
A: statistics/bytes_tx=1240159\n
A: statistics/bytes=7992753\n
A: statistics/bytes_rx=7992753\n
A: statistics/bytes_tx=1847815\n
A: statistics/errors=0\n
A: statistics/messages=194027\n
A: statistics/messages=275116\n
A: statistics/spi_async=0\n
A: statistics/spi_sync=194027\n
A: statistics/spi_sync_immediate=194027\n
A: statistics/spi_sync=275116\n
A: statistics/spi_sync_immediate=275116\n
A: statistics/timedout=0\n
A: statistics/transfer_bytes_histo_0-1=39778\n
A: statistics/transfer_bytes_histo_0-1=52216\n
A: statistics/transfer_bytes_histo_1024-2047=0\n
A: statistics/transfer_bytes_histo_128-255=718\n
A: statistics/transfer_bytes_histo_16-31=1581\n
A: statistics/transfer_bytes_histo_128-255=1468\n
A: statistics/transfer_bytes_histo_16-31=2867\n
A: statistics/transfer_bytes_histo_16384-32767=0\n
A: statistics/transfer_bytes_histo_2-3=422\n
A: statistics/transfer_bytes_histo_2-3=852\n
A: statistics/transfer_bytes_histo_2048-4095=0\n
A: statistics/transfer_bytes_histo_256-511=3907\n
A: statistics/transfer_bytes_histo_32-63=109157\n
A: statistics/transfer_bytes_histo_256-511=5797\n
A: statistics/transfer_bytes_histo_32-63=163370\n
A: statistics/transfer_bytes_histo_32768-65535=0\n
A: statistics/transfer_bytes_histo_4-7=1567\n
A: statistics/transfer_bytes_histo_4-7=2016\n
A: statistics/transfer_bytes_histo_4096-8191=0\n
A: statistics/transfer_bytes_histo_512-1023=711\n
A: statistics/transfer_bytes_histo_64-127=724\n
A: statistics/transfer_bytes_histo_64-127=759\n
A: statistics/transfer_bytes_histo_65536+=0\n
A: statistics/transfer_bytes_histo_8-15=35462\n
A: statistics/transfer_bytes_histo_8-15=45060\n
A: statistics/transfer_bytes_histo_8192-16383=0\n
A: statistics/transfers=194027\n
A: statistics/transfers=275116\n
A: statistics/transfers_split_maxsize=0\n
P: /devices/pci0000:00/0000:00:1e.3/pxa2xx-spi.8/spi_master/spi1
@ -100,33 +100,33 @@ A: power/runtime_status=unsupported\n
A: power/runtime_suspended_time=0\n
A: power/runtime_usage=0\n
L: software_node=../../../../../../kernel/software_nodes/node3
A: statistics/bytes=5478464\n
A: statistics/bytes_rx=5478464\n
A: statistics/bytes_tx=1240159\n
A: statistics/bytes=7992753\n
A: statistics/bytes_rx=7992753\n
A: statistics/bytes_tx=1847815\n
A: statistics/errors=0\n
A: statistics/messages=194027\n
A: statistics/messages=275116\n
A: statistics/spi_async=0\n
A: statistics/spi_sync=194027\n
A: statistics/spi_sync_immediate=194027\n
A: statistics/spi_sync=275116\n
A: statistics/spi_sync_immediate=275116\n
A: statistics/timedout=0\n
A: statistics/transfer_bytes_histo_0-1=39778\n
A: statistics/transfer_bytes_histo_0-1=52216\n
A: statistics/transfer_bytes_histo_1024-2047=0\n
A: statistics/transfer_bytes_histo_128-255=718\n
A: statistics/transfer_bytes_histo_16-31=1581\n
A: statistics/transfer_bytes_histo_128-255=1468\n
A: statistics/transfer_bytes_histo_16-31=2867\n
A: statistics/transfer_bytes_histo_16384-32767=0\n
A: statistics/transfer_bytes_histo_2-3=422\n
A: statistics/transfer_bytes_histo_2-3=852\n
A: statistics/transfer_bytes_histo_2048-4095=0\n
A: statistics/transfer_bytes_histo_256-511=3907\n
A: statistics/transfer_bytes_histo_32-63=109157\n
A: statistics/transfer_bytes_histo_256-511=5797\n
A: statistics/transfer_bytes_histo_32-63=163370\n
A: statistics/transfer_bytes_histo_32768-65535=0\n
A: statistics/transfer_bytes_histo_4-7=1567\n
A: statistics/transfer_bytes_histo_4-7=2016\n
A: statistics/transfer_bytes_histo_4096-8191=0\n
A: statistics/transfer_bytes_histo_512-1023=711\n
A: statistics/transfer_bytes_histo_64-127=724\n
A: statistics/transfer_bytes_histo_64-127=759\n
A: statistics/transfer_bytes_histo_65536+=0\n
A: statistics/transfer_bytes_histo_8-15=35462\n
A: statistics/transfer_bytes_histo_8-15=45060\n
A: statistics/transfer_bytes_histo_8192-16383=0\n
A: statistics/transfers=194027\n
A: statistics/transfers=275116\n
A: statistics/transfers_split_maxsize=0\n
A: waiting_for_supplier=0\n
@ -145,10 +145,10 @@ A: power/async=disabled\n
A: power/autosuspend_delay_ms=50\n
A: power/control=auto\n
A: power/runtime_active_kids=0\n
A: power/runtime_active_time=1738748\n
A: power/runtime_active_time=2111536\n
A: power/runtime_enabled=enabled\n
A: power/runtime_status=suspended\n
A: power/runtime_suspended_time=245399566\n
A: power/runtime_suspended_time=286222682\n
A: power/runtime_usage=0\n
L: software_node=../../../../kernel/software_nodes/node3
@ -191,10 +191,10 @@ A: power/async=enabled\n
A: power/control=auto\n
A: power/pm_qos_latency_tolerance_us=auto\n
A: power/runtime_active_kids=0\n
A: power/runtime_active_time=2432781\n
A: power/runtime_active_time=2944140\n
A: power/runtime_enabled=enabled\n
A: power/runtime_status=suspended\n
A: power/runtime_suspended_time=244708518\n
A: power/runtime_suspended_time=285393064\n
A: power/runtime_usage=0\n
A: power_state=D3hot\n
A: resource=0x000000007ff55000 0x000000007ff55fff 0x0000000000140204\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n