unsigned char head;
unsigned char tail;
__u16 buff[BUFFER_SIZE];
+ /* Bit to serialize writes to this device */
+#define KEYCHORD_BUSY 0x01
+ unsigned long flags;
+ wait_queue_head_t write_waitq;
};
static int check_keychord(struct keychord_device *kdev,
goto err_input_open_device;
pr_info("keychord: using input dev %s for fevent\n", dev->name);
-
return 0;
err_input_open_device:
return count;
}
+/*
+ * serializes writes on a device. can use mutex_lock_interruptible()
+ * for this particular use case as well - a matter of preference.
+ */
+static int
+keychord_write_lock(struct keychord_device *kdev)
+{
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&kdev->lock, flags);
+ while (kdev->flags & KEYCHORD_BUSY) {
+ spin_unlock_irqrestore(&kdev->lock, flags);
+ ret = wait_event_interruptible(kdev->write_waitq,
+ ((kdev->flags & KEYCHORD_BUSY) == 0));
+ if (ret)
+ return ret;
+ spin_lock_irqsave(&kdev->lock, flags);
+ }
+ kdev->flags |= KEYCHORD_BUSY;
+ spin_unlock_irqrestore(&kdev->lock, flags);
+ return 0;
+}
+
+static void
+keychord_write_unlock(struct keychord_device *kdev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&kdev->lock, flags);
+ kdev->flags &= ~KEYCHORD_BUSY;
+ spin_unlock_irqrestore(&kdev->lock, flags);
+ wake_up_interruptible(&kdev->write_waitq);
+}
+
/*
* keychord_write is used to configure the driver
*/
return -EFAULT;
}
+ /*
+ * Serialize writes to this device to prevent various races.
+ * 1) writers racing here could do duplicate input_unregister_handler()
+ * calls, resulting in attempting to unlink a node from a list that
+ * does not exist.
+ * 2) writers racing here could do duplicate input_register_handler() calls
+ * below, resulting in a duplicate insertion of a node into the list.
+ * 3) a double kfree of keychords can occur (in the event that
+ * input_register_handler() fails below.
+ */
+ ret = keychord_write_lock(kdev);
+ if (ret) {
+ kfree(keychords);
+ return ret;
+ }
+
/* unregister handler before changing configuration */
if (kdev->registered) {
input_unregister_handler(&kdev->input_handler);
if (ret) {
kfree(keychords);
kdev->keychords = 0;
+ keychord_write_unlock(kdev);
return ret;
}
kdev->registered = 1;
+ keychord_write_unlock(kdev);
+
return count;
err_unlock_return:
spin_unlock_irqrestore(&kdev->lock, flags);
kfree(keychords);
+ keychord_write_unlock(kdev);
return -EINVAL;
}
spin_lock_init(&kdev->lock);
init_waitqueue_head(&kdev->waitq);
+ init_waitqueue_head(&kdev->write_waitq);
kdev->input_handler.event = keychord_event;
kdev->input_handler.connect = keychord_connect;