summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/hid/hid-wiimote-core.c24
-rw-r--r--drivers/hid/hid-wiimote.h19
2 files changed, 38 insertions, 5 deletions
diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c
index 00a9b6fa5189..a025d2104d3c 100644
--- a/drivers/hid/hid-wiimote-core.c
+++ b/drivers/hid/hid-wiimote-core.c
@@ -58,11 +58,11 @@ static enum power_supply_property wiimote_battery_props[] = {
/* output queue handling */
-static ssize_t wiimote_hid_send(struct hid_device *hdev, __u8 *buffer,
- size_t count)
+static int wiimote_hid_send(struct hid_device *hdev, __u8 *buffer,
+ size_t count)
{
__u8 *buf;
- ssize_t ret;
+ int ret;
if (!hdev->hid_output_raw_report)
return -ENODEV;
@@ -84,14 +84,20 @@ static void wiimote_queue_worker(struct work_struct *work)
struct wiimote_data *wdata = container_of(queue, struct wiimote_data,
queue);
unsigned long flags;
+ int ret;
spin_lock_irqsave(&wdata->queue.lock, flags);
while (wdata->queue.head != wdata->queue.tail) {
spin_unlock_irqrestore(&wdata->queue.lock, flags);
- wiimote_hid_send(wdata->hdev,
+ ret = wiimote_hid_send(wdata->hdev,
wdata->queue.outq[wdata->queue.tail].data,
wdata->queue.outq[wdata->queue.tail].size);
+ if (ret < 0) {
+ spin_lock_irqsave(&wdata->state.lock, flags);
+ wiimote_cmd_abort(wdata);
+ spin_unlock_irqrestore(&wdata->state.lock, flags);
+ }
spin_lock_irqsave(&wdata->queue.lock, flags);
wdata->queue.tail = (wdata->queue.tail + 1) % WIIMOTE_BUFSIZE;
@@ -108,7 +114,9 @@ static void wiimote_queue(struct wiimote_data *wdata, const __u8 *buffer,
if (count > HID_MAX_BUFFER_SIZE) {
hid_warn(wdata->hdev, "Sending too large output report\n");
- return;
+
+ spin_lock_irqsave(&wdata->queue.lock, flags);
+ goto out_error;
}
/*
@@ -134,8 +142,14 @@ static void wiimote_queue(struct wiimote_data *wdata, const __u8 *buffer,
wdata->queue.head = newhead;
} else {
hid_warn(wdata->hdev, "Output queue is full");
+ goto out_error;
}
+ goto out_unlock;
+
+out_error:
+ wiimote_cmd_abort(wdata);
+out_unlock:
spin_unlock_irqrestore(&wdata->queue.lock, flags);
}
diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h
index 301607da7715..34417021606e 100644
--- a/drivers/hid/hid-wiimote.h
+++ b/drivers/hid/hid-wiimote.h
@@ -195,6 +195,16 @@ static inline void wiimote_cmd_complete(struct wiimote_data *wdata)
complete(&wdata->state.ready);
}
+/* requires the state.lock spinlock to be held */
+static inline void wiimote_cmd_abort(struct wiimote_data *wdata)
+{
+ /* Abort synchronous request by waking up the sleeping caller. But
+ * reset the state.cmd field to an invalid value so no further event
+ * handlers will work with it. */
+ wdata->state.cmd = WIIPROTO_REQ_MAX;
+ complete(&wdata->state.ready);
+}
+
static inline int wiimote_cmd_acquire(struct wiimote_data *wdata)
{
return mutex_lock_interruptible(&wdata->state.sync) ? -ERESTARTSYS : 0;
@@ -223,11 +233,17 @@ static inline int wiimote_cmd_wait(struct wiimote_data *wdata)
{
int ret;
+ /* The completion acts as implicit memory barrier so we can safely
+ * assume that state.cmd is set on success/failure and isn't accessed
+ * by any other thread, anymore. */
+
ret = wait_for_completion_interruptible_timeout(&wdata->state.ready, HZ);
if (ret < 0)
return -ERESTARTSYS;
else if (ret == 0)
return -EIO;
+ else if (wdata->state.cmd != WIIPROTO_REQ_NULL)
+ return -EIO;
else
return 0;
}
@@ -236,9 +252,12 @@ static inline int wiimote_cmd_wait_noint(struct wiimote_data *wdata)
{
unsigned long ret;
+ /* no locking needed; see wiimote_cmd_wait() */
ret = wait_for_completion_timeout(&wdata->state.ready, HZ);
if (!ret)
return -EIO;
+ else if (wdata->state.cmd != WIIPROTO_REQ_NULL)
+ return -EIO;
else
return 0;
}