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)