remove_dir(sd);
 }
 
+static struct sysfs_dirent *sysfs_leftmost_descendant(struct sysfs_dirent *pos)
+{
+       struct sysfs_dirent *last;
+
+       while (true) {
+               struct rb_node *rbn;
+
+               last = pos;
+
+               if (sysfs_type(pos) != SYSFS_DIR)
+                       break;
+
+               rbn = rb_first(&pos->s_dir.children);
+               if (!rbn)
+                       break;
+
+               pos = to_sysfs_dirent(rbn);
+       }
+
+       return last;
+}
+
+/**
+ * sysfs_next_descendant_post - find the next descendant for post-order walk
+ * @pos: the current position (%NULL to initiate traversal)
+ * @root: sysfs_dirent whose descendants to walk
+ *
+ * Find the next descendant to visit for post-order traversal of @root's
+ * descendants.  @root is included in the iteration and the last node to be
+ * visited.
+ */
+static struct sysfs_dirent *sysfs_next_descendant_post(struct sysfs_dirent *pos,
+                                                      struct sysfs_dirent *root)
+{
+       struct rb_node *rbn;
+
+       lockdep_assert_held(&sysfs_mutex);
+
+       /* if first iteration, visit leftmost descendant which may be root */
+       if (!pos)
+               return sysfs_leftmost_descendant(root);
+
+       /* if we visited @root, we're done */
+       if (pos == root)
+               return NULL;
+
+       /* if there's an unvisited sibling, visit its leftmost descendant */
+       rbn = rb_next(&pos->s_rb);
+       if (rbn)
+               return sysfs_leftmost_descendant(to_sysfs_dirent(rbn));
+
+       /* no sibling left, visit parent */
+       return pos->s_parent;
+}
 
 static void __sysfs_remove_dir(struct sysfs_dirent *dir_sd)
 {
        struct sysfs_addrm_cxt acxt;
-       struct rb_node *pos;
+       struct sysfs_dirent *pos, *next;
 
        if (!dir_sd)
                return;
 
        pr_debug("sysfs %s: removing dir\n", dir_sd->s_name);
        sysfs_addrm_start(&acxt);
-       pos = rb_first(&dir_sd->s_dir.children);
-       while (pos) {
-               struct sysfs_dirent *sd = to_sysfs_dirent(pos);
-               pos = rb_next(pos);
-               if (sysfs_type(sd) != SYSFS_DIR)
-                       sysfs_remove_one(&acxt, sd);
-       }
-       sysfs_addrm_finish(&acxt);
 
-       remove_dir(dir_sd);
+       next = NULL;
+       do {
+               pos = next;
+               next = sysfs_next_descendant_post(pos, dir_sd);
+               if (pos)
+                       sysfs_remove_one(&acxt, pos);
+       } while (next);
+
+       sysfs_addrm_finish(&acxt);
 }
 
 /**
  *     the directory before we remove the directory, and we've inlined
  *     what used to be sysfs_rmdir() below, instead of calling separately.
  */
-
 void sysfs_remove_dir(struct kobject *kobj)
 {
        struct sysfs_dirent *sd = kobj->sd;