]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
greybus: update operation result atomically
authorAlex Elder <elder@linaro.org>
Tue, 25 Nov 2014 22:54:02 +0000 (16:54 -0600)
committerGreg Kroah-Hartman <greg@kroah.com>
Tue, 25 Nov 2014 23:05:35 +0000 (15:05 -0800)
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>
drivers/staging/greybus/operation.c

index c3864bde520031f1eba8e572b218ad369297acb8..b4ef8201575060e9945c6ef805ceeb8791d1f69f 100644 (file)
@@ -82,18 +82,32 @@ static DEFINE_SPINLOCK(gb_operations_lock);
  */
 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;
+               /* 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 !WARN_ON(prev != -EBADR);
        }
-       operation->errno = result;
 
-       return true;
+       /* 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)