]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/commitdiff
scsi: drivers: base: Support atomic version of attribute_container_device_trigger
authorGabriel Krisman Bertazi <krisman@collabora.com>
Mon, 6 Jan 2020 18:58:15 +0000 (13:58 -0500)
committerMartin K. Petersen <martin.petersen@oracle.com>
Thu, 16 Jan 2020 03:55:36 +0000 (22:55 -0500)
attribute_container_device_trigger invokes callbacks that may fail for one
or more classdevs, for instance, the transport_add_class_device callback,
called during transport creation, does memory allocation.  This
information, though, is not propagated to upper layers, and any driver
using the attribute_container_device_trigger API will not know whether any,
some, or all callbacks succeeded.

This patch implements a safe version of this dispatcher, to either succeed
all the callbacks or revert to the original state.

Link: https://lore.kernel.org/r/20200106185817.640331-2-krisman@collabora.com
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Gabriel Krisman Bertazi <krisman@collabora.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
drivers/base/attribute_container.c
include/linux/attribute_container.h

index 20736aaa0e69ccb69660c81b7b9ae9bee0332641..f7bd0f4db13d832788e839d965c7f0bd7c8efb4e 100644 (file)
@@ -236,6 +236,109 @@ attribute_container_remove_device(struct device *dev,
        mutex_unlock(&attribute_container_mutex);
 }
 
+static int
+do_attribute_container_device_trigger_safe(struct device *dev,
+                                          struct attribute_container *cont,
+                                          int (*fn)(struct attribute_container *,
+                                                    struct device *, struct device *),
+                                          int (*undo)(struct attribute_container *,
+                                                      struct device *, struct device *))
+{
+       int ret;
+       struct internal_container *ic, *failed;
+       struct klist_iter iter;
+
+       if (attribute_container_no_classdevs(cont))
+               return fn(cont, dev, NULL);
+
+       klist_for_each_entry(ic, &cont->containers, node, &iter) {
+               if (dev == ic->classdev.parent) {
+                       ret = fn(cont, dev, &ic->classdev);
+                       if (ret) {
+                               failed = ic;
+                               klist_iter_exit(&iter);
+                               goto fail;
+                       }
+               }
+       }
+       return 0;
+
+fail:
+       if (!undo)
+               return ret;
+
+       /* Attempt to undo the work partially done. */
+       klist_for_each_entry(ic, &cont->containers, node, &iter) {
+               if (ic == failed) {
+                       klist_iter_exit(&iter);
+                       break;
+               }
+               if (dev == ic->classdev.parent)
+                       undo(cont, dev, &ic->classdev);
+       }
+       return ret;
+}
+
+/**
+ * attribute_container_device_trigger_safe - execute a trigger for each
+ * matching classdev or fail all of them.
+ *
+ * @dev:  The generic device to run the trigger for
+ * @fn   the function to execute for each classdev.
+ * @undo  A function to undo the work previously done in case of error
+ *
+ * This function is a safe version of
+ * attribute_container_device_trigger. It stops on the first error and
+ * undo the partial work that has been done, on previous classdev.  It
+ * is guaranteed that either they all succeeded, or none of them
+ * succeeded.
+ */
+int
+attribute_container_device_trigger_safe(struct device *dev,
+                                       int (*fn)(struct attribute_container *,
+                                                 struct device *,
+                                                 struct device *),
+                                       int (*undo)(struct attribute_container *,
+                                                   struct device *,
+                                                   struct device *))
+{
+       struct attribute_container *cont, *failed = NULL;
+       int ret = 0;
+
+       mutex_lock(&attribute_container_mutex);
+
+       list_for_each_entry(cont, &attribute_container_list, node) {
+
+               if (!cont->match(cont, dev))
+                       continue;
+
+               ret = do_attribute_container_device_trigger_safe(dev, cont,
+                                                                fn, undo);
+               if (ret) {
+                       failed = cont;
+                       break;
+               }
+       }
+
+       if (ret && !WARN_ON(!undo)) {
+               list_for_each_entry(cont, &attribute_container_list, node) {
+
+                       if (failed == cont)
+                               break;
+
+                       if (!cont->match(cont, dev))
+                               continue;
+
+                       do_attribute_container_device_trigger_safe(dev, cont,
+                                                                  undo, NULL);
+               }
+       }
+
+       mutex_unlock(&attribute_container_mutex);
+       return ret;
+
+}
+
 /**
  * attribute_container_device_trigger - execute a trigger for each matching classdev
  *
index d12bb2153cd698d96cd8d1c5b279cac289962b71..e4004d1e6725aa8d6c0cd798f5f18e2383db54f2 100644 (file)
@@ -54,6 +54,13 @@ void attribute_container_device_trigger(struct device *dev,
                                        int (*fn)(struct attribute_container *,
                                                  struct device *,
                                                  struct device *));
+int attribute_container_device_trigger_safe(struct device *dev,
+                                           int (*fn)(struct attribute_container *,
+                                                     struct device *,
+                                                     struct device *),
+                                           int (*undo)(struct attribute_container *,
+                                                       struct device *,
+                                                       struct device *));
 void attribute_container_trigger(struct device *dev, 
                                 int (*fn)(struct attribute_container *,
                                           struct device *));