On 10/28/2016 08:08 PM, James Bottomley wrote:
> This is a deadlock caused by an inversion issue in kernfs (suicide vs
> non-suicide removes); so fixing it in SCSI alone really isn't
> appropriate.  I count at least five other subsystems all using this
> mechanism, so they'll all be similarly affected.  It looks to be fairly
> simply fixable inside kernfs, so please fix it that way.

Hello James,

How about fixing this deadlock with the below patch?

Thanks,

Bart.

---
 drivers/base/core.c       | 45 +++++++++++++++++++++++++++++++++++++++++----
 drivers/scsi/scsi_sysfs.c | 10 +++++++---
 fs/kernfs/dir.c           | 11 ++++++++++-
 fs/sysfs/file.c           | 42 +++++++++++++++++++++++++++++++++++++++---
 include/linux/device.h    |  6 ++++++
 include/linux/kernfs.h    |  6 ++++--
 include/linux/sysfs.h     | 16 ++++++++++++++++
 7 files changed, 123 insertions(+), 13 deletions(-)

diff --git a/drivers/base/core.c b/drivers/base/core.c
index ce057a5..8f74abe 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -616,6 +616,46 @@ void device_remove_file(struct device *dev,
 }
 EXPORT_SYMBOL_GPL(device_remove_file);
 
+struct device_self_removal_data {
+       void (*cb)(struct device *, const struct device_attribute *, void *);
+       struct device                   *dev;
+       const struct device_attribute   *attr;
+       void                            *data;
+};
+
+static void device_call_cb(struct kobject *kobj, const struct attribute *attr,
+                          void *data)
+{
+       const struct device_self_removal_data *srd = data;
+
+       srd->cb(srd->dev, srd->attr, srd->data);
+}
+
+/**
+ * device_remove_file_self_cb - remove sysfs attribute file from its own method
+ * @dev: device
+ * @attr: device attribute descriptor
+ * @cb: callback function called without holding the kernfs s_active lock
+ * @data: third argument passed to @cb
+ *
+ * See kernfs_remove_self() for details.
+ */
+bool device_remove_file_self_cb(struct device *dev,
+                               const struct device_attribute *attr,
+                               void (*cb)(struct device *,
+                                          const struct device_attribute *,
+                                          void *),
+                               void *data)
+{
+       struct device_self_removal_data srd = { cb, dev, attr, data };
+
+       if (!dev)
+               return false;
+       return sysfs_remove_file_self_cb(&dev->kobj, &attr->attr, cb ?
+                                        device_call_cb : NULL, &srd);
+}
+EXPORT_SYMBOL_GPL(device_remove_file_self_cb);
+
 /**
  * device_remove_file_self - remove sysfs attribute file from its own method.
  * @dev: device.
@@ -626,10 +666,7 @@ EXPORT_SYMBOL_GPL(device_remove_file);
 bool device_remove_file_self(struct device *dev,
                             const struct device_attribute *attr)
 {
-       if (dev)
-               return sysfs_remove_file_self(&dev->kobj, &attr->attr);
-       else
-               return false;
+       return device_remove_file_self_cb(dev, attr, NULL, NULL);
 }
 EXPORT_SYMBOL_GPL(device_remove_file_self);
 
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index 0af9c91..2a30c9b 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -705,14 +705,18 @@ store_rescan_field (struct device *dev, struct 
device_attribute *attr,
 }
 static DEVICE_ATTR(rescan, S_IWUSR, NULL, store_rescan_field);
 
+static void sdev_delete_cb(struct device *dev,
+                          const struct device_attribute *attr, void *data)
+{
+       scsi_remove_device(to_scsi_device(dev));
+}
 static ssize_t
 sdev_store_delete(struct device *dev, struct device_attribute *attr,
                  const char *buf, size_t count)
 {
-       if (device_remove_file_self(dev, attr))
-               scsi_remove_device(to_scsi_device(dev));
+       device_remove_file_self_cb(dev, attr, sdev_delete_cb, NULL);
        return count;
-};
+}
 static DEVICE_ATTR(delete, S_IWUSR, NULL, sdev_store_delete);
 
 static ssize_t
diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c
index cf4c636..2831c8fa 100644
--- a/fs/kernfs/dir.c
+++ b/fs/kernfs/dir.c
@@ -1313,6 +1313,8 @@ void kernfs_unbreak_active_protection(struct kernfs_node 
*kn)
 /**
  * kernfs_remove_self - remove a kernfs_node from its own method
  * @kn: the self kernfs_node to remove
+ * @cb: callback function called without holding the kernfs s_active lock
+ * @data: second argument passed to @cb
  *
  * The caller must be running off of a kernfs operation which is invoked
  * with an active reference - e.g. one of kernfs_ops.  This can be used to
@@ -1336,7 +1338,8 @@ void kernfs_unbreak_active_protection(struct kernfs_node 
*kn)
  * guarantee, for example, all concurrent writes to a "delete" file to
  * finish only after the whole operation is complete.
  */
-bool kernfs_remove_self(struct kernfs_node *kn)
+bool kernfs_remove_self(struct kernfs_node *kn,
+                       void (*cb)(struct kernfs_node *, void *), void *data)
 {
        bool ret;
 
@@ -1354,6 +1357,12 @@ bool kernfs_remove_self(struct kernfs_node *kn)
         */
        if (!(kn->flags & KERNFS_SUICIDAL)) {
                kn->flags |= KERNFS_SUICIDAL;
+               mutex_unlock(&kernfs_mutex);
+
+               if (cb)
+                       cb(kn, data);
+
+               mutex_lock(&kernfs_mutex);
                __kernfs_remove(kn);
                kn->flags |= KERNFS_SUICIDED;
                ret = true;
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c
index b803213..e17085a 100644
--- a/fs/sysfs/file.c
+++ b/fs/sysfs/file.c
@@ -422,29 +422,65 @@ void sysfs_remove_file_ns(struct kobject *kobj, const 
struct attribute *attr,
 }
 EXPORT_SYMBOL_GPL(sysfs_remove_file_ns);
 
+struct sysfs_self_removal_data {
+       void (*cb)(struct kobject *, const struct attribute *, void *);
+       struct kobject          *kobj;
+       const struct attribute  *attr;
+       void                    *data;
+};
+
+static void sysfs_call_cb(struct kernfs_node *kn, void *data)
+{
+       const struct sysfs_self_removal_data *srd = data;
+
+       srd->cb(srd->kobj, srd->attr, srd->data);
+}
+
 /**
- * sysfs_remove_file_self - remove an object attribute from its own method
+ * sysfs_remove_file_self_cb - remove an object attribute from its own method
  * @kobj: object we're acting for
  * @attr: attribute descriptor
+ * @cb: callback function called without holding the kernfs s_active lock
+ * @data: third argument passed to @cb
  *
  * See kernfs_remove_self() for details.
  */
-bool sysfs_remove_file_self(struct kobject *kobj, const struct attribute *attr)
+bool sysfs_remove_file_self_cb(struct kobject *kobj,
+                              const struct attribute *attr,
+                              void (*cb)(struct kobject *,
+                                         const struct attribute *,
+                                         void *),
+                              void *data)
 {
        struct kernfs_node *parent = kobj->sd;
        struct kernfs_node *kn;
+       struct sysfs_self_removal_data srd = { cb, kobj, attr, data };
        bool ret;
 
        kn = kernfs_find_and_get(parent, attr->name);
        if (WARN_ON_ONCE(!kn))
                return false;
 
-       ret = kernfs_remove_self(kn);
+       ret = kernfs_remove_self(kn, cb ? sysfs_call_cb : NULL,
+                                &srd);
 
        kernfs_put(kn);
        return ret;
 }
 
+/**
+ * sysfs_remove_file_self - remove an object attribute from its own method
+ * @kobj: object we're acting for
+ * @attr: attribute descriptor
+ * @cb: callback function called without holding the kernfs s_active lock
+ *
+ * See kernfs_remove_self() for details.
+ */
+bool sysfs_remove_file_self(struct kobject *kobj, const struct attribute *attr)
+{
+       return sysfs_remove_file_self_cb(kobj, attr, NULL, NULL);
+}
+
 void sysfs_remove_files(struct kobject *kobj, const struct attribute **ptr)
 {
        int i;
diff --git a/include/linux/device.h b/include/linux/device.h
index bc41e87..55c3abc 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -597,6 +597,12 @@ extern int device_create_file(struct device *device,
                              const struct device_attribute *entry);
 extern void device_remove_file(struct device *dev,
                               const struct device_attribute *attr);
+extern bool device_remove_file_self_cb(struct device *dev,
+                                      const struct device_attribute *attr,
+                                      void (*cb)(struct device *,
+                                               const struct device_attribute *,
+                                               void *),
+                                      void *data);
 extern bool device_remove_file_self(struct device *dev,
                                    const struct device_attribute *attr);
 extern int __must_check device_create_bin_file(struct device *dev,
diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h
index 7056238..14f3ef2 100644
--- a/include/linux/kernfs.h
+++ b/include/linux/kernfs.h
@@ -309,7 +309,8 @@ void kernfs_activate(struct kernfs_node *kn);
 void kernfs_remove(struct kernfs_node *kn);
 void kernfs_break_active_protection(struct kernfs_node *kn);
 void kernfs_unbreak_active_protection(struct kernfs_node *kn);
-bool kernfs_remove_self(struct kernfs_node *kn);
+bool kernfs_remove_self(struct kernfs_node *kn,
+                       void (*cb)(struct kernfs_node *, void *), void *data);
 int kernfs_remove_by_name_ns(struct kernfs_node *parent, const char *name,
                             const void *ns);
 int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent,
@@ -399,7 +400,8 @@ static inline void kernfs_activate(struct kernfs_node *kn) 
{ }
 
 static inline void kernfs_remove(struct kernfs_node *kn) { }
 
-static inline bool kernfs_remove_self(struct kernfs_node *kn)
+static inline bool kernfs_remove_self(struct kernfs_node *kn,
+                                     void (*cb)(void *), void *data)
 { return false; }
 
 static inline int kernfs_remove_by_name_ns(struct kernfs_node *kn,
diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h
index c6f0f0d..69f465e 100644
--- a/include/linux/sysfs.h
+++ b/include/linux/sysfs.h
@@ -234,6 +234,12 @@ int __must_check sysfs_chmod_file(struct kobject *kobj,
                                  const struct attribute *attr, umode_t mode);
 void sysfs_remove_file_ns(struct kobject *kobj, const struct attribute *attr,
                          const void *ns);
+bool sysfs_remove_file_self_cb(struct kobject *kobj,
+                              const struct attribute *attr,
+                              void (*cb)(struct kobject *,
+                                         const struct attribute *,
+                                         void *),
+                              void *data);
 bool sysfs_remove_file_self(struct kobject *kobj, const struct attribute 
*attr);
 void sysfs_remove_files(struct kobject *kobj, const struct attribute **attr);
 
@@ -351,6 +357,16 @@ static inline void sysfs_remove_file_ns(struct kobject 
*kobj,
 {
 }
 
+static inline bool sysfs_remove_file_self_cb(struct kobject *kobj,
+                                            const struct attribute *attr,
+                                            void (*cb)(struct kobject *,
+                                               const struct attribute *,
+                                               void *),
+                              void *data);
+{
+       return false;
+}
+
 static inline bool sysfs_remove_file_self(struct kobject *kobj,
                                          const struct attribute *attr)
 {
-- 
2.10.1


--
To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to