__xfs_iunpin_wait(ip);
 }
 
+/*
+ * Removing an inode from the namespace involves removing the directory entry
+ * and dropping the link count on the inode. Removing the directory entry can
+ * result in locking an AGF (directory blocks were freed) and removing a link
+ * count can result in placing the inode on an unlinked list which results in
+ * locking an AGI.
+ *
+ * The big problem here is that we have an ordering constraint on AGF and AGI
+ * locking - inode allocation locks the AGI, then can allocate a new extent for
+ * new inodes, locking the AGF after the AGI. Similarly, freeing the inode
+ * removes the inode from the unlinked list, requiring that we lock the AGI
+ * first, and then freeing the inode can result in an inode chunk being freed
+ * and hence freeing disk space requiring that we lock an AGF.
+ *
+ * Hence the ordering that is imposed by other parts of the code is AGI before
+ * AGF. This means we cannot remove the directory entry before we drop the inode
+ * reference count and put it on the unlinked list as this results in a lock
+ * order of AGF then AGI, and this can deadlock against inode allocation and
+ * freeing. Therefore we must drop the link counts before we remove the
+ * directory entry.
+ *
+ * This is still safe from a transactional point of view - it is not until we
+ * get to xfs_bmap_finish() that we have the possibility of multiple
+ * transactions in this operation. Hence as long as we remove the directory
+ * entry and drop the link count in the first transaction of the remove
+ * operation, there are no transactional constraints on the ordering here.
+ */
 int
 xfs_remove(
        xfs_inode_t             *dp,
        /*
         * If we're removing a directory perform some additional validation.
         */
+       cancel_flags |= XFS_TRANS_ABORT;
        if (is_dir) {
                ASSERT(ip->i_d.di_nlink >= 2);
                if (ip->i_d.di_nlink != 2) {
                        error = XFS_ERROR(ENOTEMPTY);
                        goto out_trans_cancel;
                }
-       }
 
-       xfs_bmap_init(&free_list, &first_block);
-       error = xfs_dir_removename(tp, dp, name, ip->i_ino,
-                                       &first_block, &free_list, resblks);
-       if (error) {
-               ASSERT(error != ENOENT);
-               goto out_bmap_cancel;
-       }
-       xfs_trans_ichgtime(tp, dp, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
-
-       if (is_dir) {
-               /*
-                * Drop the link from ip's "..".
-                */
+               /* Drop the link from ip's "..".  */
                error = xfs_droplink(tp, dp);
                if (error)
-                       goto out_bmap_cancel;
+                       goto out_trans_cancel;
 
-               /*
-                * Drop the "." link from ip to self.
-                */
+               /* Drop the "." link from ip to self.  */
                error = xfs_droplink(tp, ip);
                if (error)
-                       goto out_bmap_cancel;
+                       goto out_trans_cancel;
        } else {
                /*
                 * When removing a non-directory we need to log the parent
                 */
                xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE);
        }
+       xfs_trans_ichgtime(tp, dp, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
 
-       /*
-        * Drop the link from dp to ip.
-        */
+       /* Drop the link from dp to ip. */
        error = xfs_droplink(tp, ip);
        if (error)
-               goto out_bmap_cancel;
+               goto out_trans_cancel;
 
-       /*
-        * Determine if this is the last link while
-        * we are in the transaction.
-        */
+       /* Determine if this is the last link while the inode is locked */
        link_zero = (ip->i_d.di_nlink == 0);
 
+       xfs_bmap_init(&free_list, &first_block);
+       error = xfs_dir_removename(tp, dp, name, ip->i_ino,
+                                       &first_block, &free_list, resblks);
+       if (error) {
+               ASSERT(error != ENOENT);
+               goto out_bmap_cancel;
+       }
+
        /*
         * If this is a synchronous mount, make sure that the
         * remove transaction goes to disk before returning to
 
  out_bmap_cancel:
        xfs_bmap_cancel(&free_list);
-       cancel_flags |= XFS_TRANS_ABORT;
  out_trans_cancel:
        xfs_trans_cancel(tp, cancel_flags);
  std_return: