aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Medhurst <tixy@linaro.org>2015-10-27 13:36:11 +0000
committerJon Medhurst <tixy@linaro.org>2016-10-03 09:44:32 +0100
commitec56d615d80d0cc9e5e85b4df32c7cc6ad013f83 (patch)
tree44a263945561cc1af0ecad1ce2f59f353c739760
parent451f8cbafe438c8f68581b715c7711b36d76c926 (diff)
firmware: arm_scpi: Properly implement wait for messages to complete
scpi_send_message is broken for a least two reasons. 1. The driver sets tx_block on the mailbox client and expects mbox_send_message to block until the message is sent, however this isn't the case. Whether by design or in error, mbox_send_message waits for tx_complete which will be signalled when any message is transmitted, not the one just queued. So in the case where two threads send messages concurrently, one thread may see the signal from the other's message completing, whilst it's message is still in the queue. 2. The second flaw is that even if the mailbox framework was waiting for the correct message signal the driver has set a timeout (mailbox client tx_tout) which means the wait for that signal may be aborted whilst the message is still in the queue for sending. Both the above mean that when mbox_send_message returns the message may still be pending transmission so we cannot safely do anything with its resources. The current code however goes on to free the message with put_scpi_xfer resulting in all sorts of bugs. To fix this, we add code to do a proper wait for the message to complete and if a timeout occurs BUG because we don't have any way to cancel messages in the queue. Signed-off-by: Jon Medhurst <tixy@linaro.org>
-rw-r--r--drivers/firmware/arm_scpi.c42
1 files changed, 29 insertions, 13 deletions
diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c
index ce2bc2a38101..8be8a04e6ed2 100644
--- a/drivers/firmware/arm_scpi.c
+++ b/drivers/firmware/arm_scpi.c
@@ -80,8 +80,6 @@
#define FW_REV_MINOR(x) (((x) & FW_REV_MINOR_MASK) >> FW_REV_MINOR_BITS)
#define FW_REV_PATCH(x) ((x) & FW_REV_PATCH_MASK)
-#define MAX_RX_TIMEOUT (msecs_to_jiffies(30))
-
enum scpi_error_codes {
SCPI_SUCCESS = 0, /* Success */
SCPI_ERR_PARAM = 1, /* Invalid parameter(s) */
@@ -322,6 +320,18 @@ static void scpi_tx_prepare(struct mbox_client *c, void *msg)
mem->command = cpu_to_le32(t->cmd);
}
+static void scpi_tx_done(struct mbox_client *c, void *msg, int result)
+{
+ struct scpi_xfer *t = msg;
+
+ if (!t->rx_buf)
+ complete(&t->done);
+ /*
+ * Messages with rx_buf are expecting a reply and will be on the
+ * rx_pending list, so leave them alone.
+ */
+}
+
static struct scpi_xfer *get_scpi_xfer(struct scpi_chan *ch)
{
struct scpi_xfer *t;
@@ -368,17 +378,24 @@ static int scpi_send_message(u8 cmd, void *tx_buf, unsigned int tx_len,
init_completion(&msg->done);
ret = mbox_send_message(scpi_chan->chan, msg);
- if (ret < 0 || !rx_buf)
- goto out;
+ if (ret >= 0) {
+ /*
+ * Wait for message to be processed. If we end up having to wait
+ * for a very long time then there is a serious bug, probably in
+ * the firmware.
+ *
+ * IMPORTANT: We must not try and continue after the timeout
+ * because this driver and the mailbox framework still has data
+ * structures referring to the failed request and further
+ * serious bugs will result.
+ */
+ if (!wait_for_completion_timeout(&msg->done, msecs_to_jiffies(10000)))
+ BUG();
- if (!wait_for_completion_timeout(&msg->done, MAX_RX_TIMEOUT))
- ret = -ETIMEDOUT;
- else
/* first status word */
- ret = msg->status;
-out:
- if (ret < 0 && rx_buf) /* remove entry from the list if timed-out */
- scpi_process_cmd(scpi_chan, msg->cmd);
+ if (rx_buf)
+ ret = le32_to_cpu(msg->status);
+ }
put_scpi_xfer(msg, scpi_chan);
/* SCPI error codes > 0, translate them to Linux scale*/
@@ -728,8 +745,7 @@ static int scpi_probe(struct platform_device *pdev)
cl->dev = dev;
cl->rx_callback = scpi_handle_remote_msg;
cl->tx_prepare = scpi_tx_prepare;
- cl->tx_block = true;
- cl->tx_tout = 20;
+ cl->tx_done = scpi_tx_done;
cl->knows_txdone = false; /* controller can't ack */
INIT_LIST_HEAD(&pchan->rx_pending);