From 5c89bda7f319bad8cb79b5715a534a9221f4c331 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Thu, 20 May 2021 20:01:49 +0200 Subject: [PATCH] synaptics: Implement suspend/resume methods We only allow suspending while we are in the interrupt transfer stage. To suspend, we cancel the interrupt transfer and at resume time we restart it. This has been tested to work correctly on an X1 Carbon 8th Gen with suspend mode set to "Windows 10" (i.e. S0ix [s2idle] and not S3 [suspend to RAM]). With S3 suspend, the USB root hub appears to be turned off or reset and the device will be unresponsive afterwards (if it returns). To avoid issues, libfprint disables the "persist" mode in the kernel and we'll see a new device instead after resume. --- libfprint/drivers/synaptics/synaptics.c | 78 ++++++++++++++++++++++++- libfprint/drivers/synaptics/synaptics.h | 3 + libfprint/fp-device.c | 2 +- 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/libfprint/drivers/synaptics/synaptics.c b/libfprint/drivers/synaptics/synaptics.c index e3ef238..fbafc18 100644 --- a/libfprint/drivers/synaptics/synaptics.c +++ b/libfprint/drivers/synaptics/synaptics.c @@ -198,12 +198,17 @@ cmd_interrupt_cb (FpiUsbTransfer *transfer, GError *error) { g_debug ("interrupt transfer done"); + fpi_device_critical_enter (device); + if (error) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_error_free (error); - fpi_ssm_jump_to_state (transfer->ssm, SYNAPTICS_CMD_GET_RESP); + if (FPI_DEVICE_SYNAPTICS (device)->cmd_suspended) + fpi_ssm_jump_to_state (transfer->ssm, SYNAPTICS_CMD_SUSPENDED); + else + fpi_ssm_jump_to_state (transfer->ssm, SYNAPTICS_CMD_GET_RESP); return; } @@ -264,6 +269,9 @@ synaptics_cmd_run_state (FpiSsm *ssm, break; case SYNAPTICS_CMD_WAIT_INTERRUPT: + /* Interruptions are permitted only during an interrupt transfer */ + fpi_device_critical_leave (dev); + transfer = fpi_usb_transfer_new (dev); transfer->ssm = ssm; fpi_usb_transfer_fill_interrupt (transfer, USB_EP_INTERRUPT, USB_INTERRUPT_DATA_SIZE); @@ -291,6 +299,17 @@ synaptics_cmd_run_state (FpiSsm *ssm, case SYNAPTICS_CMD_RESTART: fpi_ssm_jump_to_state (ssm, SYNAPTICS_CMD_SEND_PENDING); break; + + case SYNAPTICS_CMD_SUSPENDED: + /* The resume handler continues to the next state! */ + fpi_device_critical_leave (dev); + fpi_device_suspend_complete (dev, NULL); + break; + + case SYNAPTICS_CMD_RESUME: + fpi_device_critical_enter (dev); + fpi_ssm_jump_to_state (ssm, SYNAPTICS_CMD_WAIT_INTERRUPT); + break; } } @@ -306,6 +325,7 @@ cmd_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) if (error || self->cmd_complete_on_removal) callback (self, NULL, error); + fpi_device_critical_leave (dev); self->cmd_complete_on_removal = FALSE; } @@ -415,6 +435,7 @@ synaptics_sensor_cmd (FpiDeviceSynaptics *self, SYNAPTICS_CMD_NUM_STATES); fpi_ssm_set_data (self->cmd_ssm, callback, NULL); + fpi_device_critical_enter (FP_DEVICE (self)); fpi_ssm_start (self->cmd_ssm, cmd_ssm_done); } } @@ -1399,6 +1420,59 @@ cancel (FpDevice *dev) self->interrupt_cancellable = g_cancellable_new (); } +static void +suspend (FpDevice *dev) +{ + FpiDeviceSynaptics *self = FPI_DEVICE_SYNAPTICS (dev); + FpiDeviceAction action = fpi_device_get_current_action (dev); + + g_debug ("got suspend request"); + + if (action != FPI_DEVICE_ACTION_VERIFY && action != FPI_DEVICE_ACTION_IDENTIFY) + { + fpi_device_suspend_complete (dev, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); + return; + } + + /* We are guaranteed to have a cmd_ssm running at this time. */ + g_assert (self->cmd_ssm); + g_assert (fpi_ssm_get_cur_state (self->cmd_ssm) == SYNAPTICS_CMD_WAIT_INTERRUPT); + self->cmd_suspended = TRUE; + + /* Cancel the current transfer. + * The CMD SSM will go into the suspend state and signal readyness. */ + g_cancellable_cancel (self->interrupt_cancellable); + g_clear_object (&self->interrupt_cancellable); + self->interrupt_cancellable = g_cancellable_new (); +} + +static void +resume (FpDevice *dev) +{ + FpiDeviceSynaptics *self = FPI_DEVICE_SYNAPTICS (dev); + FpiDeviceAction action = fpi_device_get_current_action (dev); + + g_debug ("got resume request"); + + if (action != FPI_DEVICE_ACTION_VERIFY && action != FPI_DEVICE_ACTION_IDENTIFY) + { + g_assert_not_reached (); + fpi_device_resume_complete (dev, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); + return; + } + + /* We must have a suspended cmd_ssm at this point */ + g_assert (self->cmd_ssm); + g_assert (self->cmd_suspended); + g_assert (fpi_ssm_get_cur_state (self->cmd_ssm) == SYNAPTICS_CMD_SUSPENDED); + self->cmd_suspended = FALSE; + + /* Restart interrupt transfer. */ + fpi_ssm_jump_to_state (self->cmd_ssm, SYNAPTICS_CMD_RESUME); + + fpi_device_resume_complete (dev, NULL); +} + static void fpi_device_synaptics_init (FpiDeviceSynaptics *self) { @@ -1427,6 +1501,8 @@ fpi_device_synaptics_class_init (FpiDeviceSynapticsClass *klass) dev_class->delete = delete_print; dev_class->clear_storage = clear_storage; dev_class->cancel = cancel; + dev_class->suspend = suspend; + dev_class->resume = resume; fpi_device_class_auto_initialize_features (dev_class); } diff --git a/libfprint/drivers/synaptics/synaptics.h b/libfprint/drivers/synaptics/synaptics.h index 2ed09f8..5fc0a19 100644 --- a/libfprint/drivers/synaptics/synaptics.h +++ b/libfprint/drivers/synaptics/synaptics.h @@ -93,6 +93,8 @@ typedef enum { SYNAPTICS_CMD_WAIT_INTERRUPT, SYNAPTICS_CMD_SEND_ASYNC, SYNAPTICS_CMD_RESTART, + SYNAPTICS_CMD_SUSPENDED, + SYNAPTICS_CMD_RESUME, SYNAPTICS_CMD_NUM_STATES, } SynapticsCmdState; @@ -110,6 +112,7 @@ struct _FpiDeviceSynaptics FpiSsm *cmd_ssm; FpiUsbTransfer *cmd_pending_transfer; gboolean cmd_complete_on_removal; + gboolean cmd_suspended; guint8 id_idx; bmkt_sensor_version_t mis_version; diff --git a/libfprint/fp-device.c b/libfprint/fp-device.c index 209c418..4d19bf0 100644 --- a/libfprint/fp-device.c +++ b/libfprint/fp-device.c @@ -1126,7 +1126,7 @@ fp_device_resume (FpDevice *device, case FPI_DEVICE_ACTION_LIST: case FPI_DEVICE_ACTION_CLEAR_STORAGE: /* cannot happen as we make sure these tasks complete before suspend */ - g_assert_not_reached(); + g_assert_not_reached (); complete_suspend_resume_task (device); break; }