hwmon: (corsair-cpro) Protect ccp->wait_input_report with a spinlock
authorAleksa Savic <savicaleksa83@gmail.com>
Sat, 4 May 2024 09:25:03 +0000 (11:25 +0200)
committerGuenter Roeck <linux@roeck-us.net>
Sat, 4 May 2024 13:36:41 +0000 (06:36 -0700)
Through hidraw, userspace can cause a status report to be sent
from the device. The parsing in ccp_raw_event() may happen in
parallel to a send_usb_cmd() call (which resets the completion
for tracking the report) if it's running on a different CPU where
bottom half interrupts are not disabled.

Add a spinlock around the complete_all() in ccp_raw_event() and
reinit_completion() in send_usb_cmd() to prevent race issues.

Fixes: 40c3a4454225 ("hwmon: add Corsair Commander Pro driver")
Signed-off-by: Aleksa Savic <savicaleksa83@gmail.com>
Acked-by: Marius Zachmann <mail@mariuszachmann.de>
Link: https://lore.kernel.org/r/20240504092504.24158-4-savicaleksa83@gmail.com
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
drivers/hwmon/corsair-cpro.c

index 6ab4d2478b1fd6f1533c39a3170ecf26235653bd..3e63666a61bd6b275e7140def18184c968e3fd52 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/slab.h>
+#include <linux/spinlock.h>
 #include <linux/types.h>
 
 #define USB_VENDOR_ID_CORSAIR                  0x1b1c
@@ -77,6 +78,8 @@
 struct ccp_device {
        struct hid_device *hdev;
        struct device *hwmon_dev;
+       /* For reinitializing the completion below */
+       spinlock_t wait_input_report_lock;
        struct completion wait_input_report;
        struct mutex mutex; /* whenever buffer is used, lock before send_usb_cmd */
        u8 *cmd_buffer;
@@ -118,7 +121,15 @@ static int send_usb_cmd(struct ccp_device *ccp, u8 command, u8 byte1, u8 byte2,
        ccp->cmd_buffer[2] = byte2;
        ccp->cmd_buffer[3] = byte3;
 
+       /*
+        * Disable raw event parsing for a moment to safely reinitialize the
+        * completion. Reinit is done because hidraw could have triggered
+        * the raw event parsing and marked the ccp->wait_input_report
+        * completion as done.
+        */
+       spin_lock_bh(&ccp->wait_input_report_lock);
        reinit_completion(&ccp->wait_input_report);
+       spin_unlock_bh(&ccp->wait_input_report_lock);
 
        ret = hid_hw_output_report(ccp->hdev, ccp->cmd_buffer, OUT_BUFFER_SIZE);
        if (ret < 0)
@@ -136,11 +147,12 @@ static int ccp_raw_event(struct hid_device *hdev, struct hid_report *report, u8
        struct ccp_device *ccp = hid_get_drvdata(hdev);
 
        /* only copy buffer when requested */
-       if (completion_done(&ccp->wait_input_report))
-               return 0;
-
-       memcpy(ccp->buffer, data, min(IN_BUFFER_SIZE, size));
-       complete_all(&ccp->wait_input_report);
+       spin_lock(&ccp->wait_input_report_lock);
+       if (!completion_done(&ccp->wait_input_report)) {
+               memcpy(ccp->buffer, data, min(IN_BUFFER_SIZE, size));
+               complete_all(&ccp->wait_input_report);
+       }
+       spin_unlock(&ccp->wait_input_report_lock);
 
        return 0;
 }
@@ -515,7 +527,9 @@ static int ccp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 
        ccp->hdev = hdev;
        hid_set_drvdata(hdev, ccp);
+
        mutex_init(&ccp->mutex);
+       spin_lock_init(&ccp->wait_input_report_lock);
        init_completion(&ccp->wait_input_report);
 
        hid_device_io_start(hdev);