Incoming events can schedule reset work before transport initialization
completes or re-queue it during suspend and removal, power management
callbacks can dereference uninitialized device structures on failure,
and failed resume steps can leave transport interrupts disabled.
Gate reset work scheduling and power management callbacks on device
state, track suspended state to prevent work execution, ensure resume
re-enables transport interrupts, and permanently disable reset work on
removal.
Fixes: 0b2c7a897378 ("HID: rmi: Make hid-rmi a transport driver for synaptics-rmi4")
Fixes: 8725aa4fa7de ("HID: rmi: Check that the RMI_STARTED bit is set before unregistering the RMI transport device")
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-rmi.c | 35 ++++++++++++++++++++++-------------
1 file changed, 22 insertions(+), 13 deletions(-)
diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
index d9181fd66c5c..8783edb7fd6a 100644
--- a/drivers/hid/hid-rmi.c
+++ b/drivers/hid/hid-rmi.c
@@ -31,6 +31,7 @@
#define RMI_READ_REQUEST_PENDING 0
#define RMI_READ_DATA_PENDING 1
#define RMI_STARTED 2
+#define RMI_SUSPENDED 3
/* device flags */
#define RMI_DEVICE BIT(0)
@@ -428,6 +429,8 @@ static int rmi_event(struct hid_device *hdev, struct hid_field *field,
struct rmi_data *data = hid_get_drvdata(hdev);
if ((data->device_flags & RMI_DEVICE) &&
+ test_bit(RMI_STARTED, &data->flags) &&
+ !test_bit(RMI_SUSPENDED, &data->flags) &&
(field->application == HID_GD_POINTER ||
field->application == HID_GD_MOUSE)) {
if (data->device_flags & RMI_DEVICE_HAS_PHYS_BUTTONS) {@@ -469,12 +472,16 @@ static int rmi_suspend(struct hid_device *hdev, pm_message_t message)
struct rmi_device *rmi_dev = data->xport.rmi_dev;
int ret;
- if (!(data->device_flags & RMI_DEVICE))
+ if (!test_bit(RMI_STARTED, &data->flags))
return 0;
+ set_bit(RMI_SUSPENDED, &data->flags);
+ cancel_work_sync(&data->reset_work);
+
ret = rmi_driver_suspend(rmi_dev, false);
if (ret) {
hid_warn(hdev, "Failed to suspend device: %d\n", ret);
+ clear_bit(RMI_SUSPENDED, &data->flags);
return ret;
}
@@ -486,27 +493,31 @@ static int rmi_post_resume(struct hid_device *hdev)
struct rmi_data *data = hid_get_drvdata(hdev);
struct rmi_device *rmi_dev = data->xport.rmi_dev;
int ret;
+ int err;
- if (!(data->device_flags & RMI_DEVICE))
+ if (!test_bit(RMI_STARTED, &data->flags))
return 0;
/* Make sure the HID device is ready to receive events */
ret = hid_hw_open(hdev);
if (ret)
- return ret;
+ goto out;
ret = rmi_reset_attn_mode(hdev);
if (ret)
- goto out;
+ hid_warn(hdev, "Failed to reset attention mode: %d\n", ret);
- ret = rmi_driver_resume(rmi_dev, false);
- if (ret) {
- hid_warn(hdev, "Failed to resume device: %d\n", ret);
- goto out;
+ err = rmi_driver_resume(rmi_dev, false);
+ if (err) {
+ hid_warn(hdev, "Failed to resume device: %d\n", err);
+ if (!ret)
+ ret = err;
}
-out:
hid_hw_close(hdev);
+
+out:
+ clear_bit(RMI_SUSPENDED, &data->flags);
return ret;
}
@@ -787,10 +798,8 @@ static void rmi_remove(struct hid_device *hdev)
{
struct rmi_data *hdata = hid_get_drvdata(hdev);
- if ((hdata->device_flags & RMI_DEVICE)
- && test_bit(RMI_STARTED, &hdata->flags)) {
- clear_bit(RMI_STARTED, &hdata->flags);
- cancel_work_sync(&hdata->reset_work);
+ if (test_and_clear_bit(RMI_STARTED, &hdata->flags)) {
+ disable_work_sync(&hdata->reset_work);
rmi_unregister_transport_device(&hdata->xport);
}
--
2.56.0.679.gc527c65306-goog