greybus: update operation result atomically

An operation result can be set both in and out of interrupt context.
For example, a response message could be arriving at the same time a
timeout of the operation is getting processed.  We therefore need to
ensure the result is accessed atomically.

Protect updates to the errno field using the operations spinlock.

Signed-off-by: Alex Elder <elder@linaro.org>
Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
diff --git a/drivers/staging/greybus/operation.c b/drivers/staging/greybus/operation.c
index c3864bd..b4ef820 100644
--- a/drivers/staging/greybus/operation.c
+++ b/drivers/staging/greybus/operation.c
@@ -82,18 +82,32 @@
  */
 static bool gb_operation_result_set(struct gb_operation *operation, int result)
 {
+	int prev;
+
+	/* Nobody should be setting -EBADR */
 	if (WARN_ON(result == -EBADR))
 		return false;
 
+	/* Are we sending the request message? */
 	if (result == -EINPROGRESS) {
-		if (WARN_ON(operation->errno != -EBADR))
-			return false;
-	} else if (operation->errno != -EINPROGRESS) {
-		return false;
-	}
-	operation->errno = result;
+		/* Yes, but verify the result has not already been set */
+		spin_lock_irq(&gb_operations_lock);
+		prev = operation->errno;
+		if (prev == -EBADR)
+			operation->errno = result;
+		spin_unlock_irq(&gb_operations_lock);
 
-	return true;
+		return !WARN_ON(prev != -EBADR);
+	}
+
+	/* Trying to set final status; only the first one succeeds */
+	spin_lock_irq(&gb_operations_lock);
+	prev = operation->errno;
+	if (prev == -EINPROGRESS)
+		operation->errno = result;
+	spin_unlock_irq(&gb_operations_lock);
+
+	return prev == -EINPROGRESS;
 }
 
 int gb_operation_result(struct gb_operation *operation)