diff --git a/fs/xfs/scrub/array.c b/fs/xfs/scrub/array.c
index 47028449071e..7e1fef3c947a 100644
--- a/fs/xfs/scrub/array.c
+++ b/fs/xfs/scrub/array.c
@@ -66,6 +66,7 @@ xfbma_init(
 	array->filp = filp;
 	array->obj_size = obj_size;
 	array->nr = 0;
+	array->io_flags = 0;
 	return array;
 out_filp:
 	fput(filp);
@@ -105,7 +106,8 @@ xfbma_get(
 		return -ENODATA;
 	}
 
-	return xfile_io(array->filp, XFILE_IO_READ, &pos, ptr, array->obj_size);
+	return xfile_io(array->filp, array->io_flags | XFILE_IO_READ, &pos,
+			ptr, array->obj_size);
 }
 
 /* Put an element in the array. */
@@ -122,8 +124,8 @@ xfbma_set(
 		return -ENODATA;
 	}
 
-	return xfile_io(array->filp, XFILE_IO_WRITE, &pos, ptr,
-			array->obj_size);
+	return xfile_io(array->filp, array->io_flags | XFILE_IO_WRITE, &pos,
+			ptr, array->obj_size);
 }
 
 /* Is this array element NULL? */
@@ -172,8 +174,8 @@ xfbma_nullify(
 	}
 
 	memset(temp, 0, array->obj_size);
-	return xfile_io(array->filp, XFILE_IO_WRITE, &pos, temp,
-			array->obj_size);
+	return xfile_io(array->filp, array->io_flags | XFILE_IO_WRITE, &pos,
+			temp, array->obj_size);
 }
 
 /* Append an element to the array. */
@@ -190,8 +192,8 @@ xfbma_append(
 		return -ENODATA;
 	}
 
-	error = xfile_io(array->filp, XFILE_IO_WRITE, &pos, ptr,
-			array->obj_size);
+	error = xfile_io(array->filp, array->io_flags | XFILE_IO_WRITE, &pos,
+			ptr, array->obj_size);
 	if (error)
 		return error;
 	array->nr++;
@@ -219,8 +221,8 @@ xfbma_iter_del(
 	for (pos = 0, i = 0; pos < max_bytes; i++) {
 		pgoff_t	pagenr;
 
-		error = xfile_io(array->filp, XFILE_IO_READ, &pos, temp,
-				array->obj_size);
+		error = xfile_io(array->filp, array->io_flags | XFILE_IO_READ,
+				&pos, temp, array->obj_size);
 		if (error)
 			break;
 		if (xfbma_is_null(array, temp))
diff --git a/fs/xfs/scrub/array.h b/fs/xfs/scrub/array.h
index 77b7f6005da4..6ce40c2e61f1 100644
--- a/fs/xfs/scrub/array.h
+++ b/fs/xfs/scrub/array.h
@@ -10,6 +10,7 @@ struct xfbma {
 	struct file	*filp;
 	size_t		obj_size;
 	uint64_t	nr;
+	unsigned int	io_flags;
 };
 
 struct xfbma *xfbma_init(size_t obj_size);
diff --git a/fs/xfs/scrub/blob.c b/fs/xfs/scrub/blob.c
index 94912fcb1fd1..30e189a8bd3c 100644
--- a/fs/xfs/scrub/blob.c
+++ b/fs/xfs/scrub/blob.c
@@ -46,6 +46,7 @@ xblob_init(void)
 
 	blob->filp = filp;
 	blob->last_offset = PAGE_SIZE;
+	blob->io_flags = 0;
 	return blob;
 out_filp:
 	fput(filp);
@@ -73,7 +74,8 @@ xblob_get(
 	loff_t		pos = cookie;
 	int		error;
 
-	error = xfile_io(blob->filp, XFILE_IO_READ, &pos, &key, sizeof(key));
+	error = xfile_io(blob->filp, blob->io_flags | XFILE_IO_READ, &pos,
+			&key, sizeof(key));
 	if (error)
 		return error;
 
@@ -86,7 +88,8 @@ xblob_get(
 		return -EFBIG;
 	}
 
-	return xfile_io(blob->filp, XFILE_IO_READ, &pos, ptr, key.size);
+	return xfile_io(blob->filp, blob->io_flags | XFILE_IO_READ, &pos, ptr,
+			key.size);
 }
 
 /* Store a blob. */
@@ -105,11 +108,13 @@ xblob_put(
 	loff_t		pos = blob->last_offset;
 	int		error;
 
-	error = xfile_io(blob->filp, XFILE_IO_WRITE, &pos, &key, sizeof(key));
+	error = xfile_io(blob->filp, blob->io_flags | XFILE_IO_WRITE, &pos,
+			&key, sizeof(key));
 	if (error)
 		goto out_err;
 
-	error = xfile_io(blob->filp, XFILE_IO_WRITE, &pos, ptr, size);
+	error = xfile_io(blob->filp, blob->io_flags | XFILE_IO_WRITE, &pos,
+			ptr, size);
 	if (error)
 		goto out_err;
 
@@ -131,7 +136,8 @@ xblob_free(
 	loff_t		pos = cookie;
 	int		error;
 
-	error = xfile_io(blob->filp, XFILE_IO_READ, &pos, &key, sizeof(key));
+	error = xfile_io(blob->filp, blob->io_flags | XFILE_IO_READ, &pos,
+			&key, sizeof(key));
 	if (error)
 		return error;
 
diff --git a/fs/xfs/scrub/blob.h b/fs/xfs/scrub/blob.h
index c6f6c6a2e084..77b515aa4d21 100644
--- a/fs/xfs/scrub/blob.h
+++ b/fs/xfs/scrub/blob.h
@@ -9,6 +9,7 @@
 struct xblob {
 	struct file	*filp;
 	loff_t		last_offset;
+	unsigned int	io_flags;
 };
 
 typedef loff_t		xblob_cookie;
diff --git a/fs/xfs/scrub/xfile.c b/fs/xfs/scrub/xfile.c
index 2d96e2f9917c..504f1aa30c61 100644
--- a/fs/xfs/scrub/xfile.c
+++ b/fs/xfs/scrub/xfile.c
@@ -41,14 +41,76 @@ xfile_destroy(
 	fput(filp);
 }
 
+struct xfile_io_args {
+	struct work_struct	work;
+	struct completion	*done;
+
+	struct file		*filp;
+	void			*ptr;
+	loff_t			*pos;
+	size_t			count;
+	ssize_t			ret;
+	bool			is_read;
+};
+
+static void
+xfile_io_worker(
+	struct work_struct	*work)
+{
+	struct xfile_io_args	*args;
+	unsigned int		pflags;
+
+	args = container_of(work, struct xfile_io_args, work);
+	pflags = memalloc_nofs_save();
+
+	if (args->is_read)
+		args->ret = kernel_read(args->filp, args->ptr, args->count,
+				args->pos);
+	else
+		args->ret = kernel_write(args->filp, args->ptr, args->count,
+				args->pos);
+	complete(args->done);
+
+	memalloc_nofs_restore(pflags);
+}
+
 /*
- * Perform a read or write IO to the file backing the array.  We can defer
- * the work to a workqueue if the caller so desires, either to reduce stack
- * usage or because the xfs is frozen and we want to avoid deadlocking on the
- * page fault that might be about to happen.
+ * Perform a read or write IO to the file backing the array.  Defer the work to
+ * a workqueue to avoid recursing into the filesystem while we have locks held.
  */
-int
-xfile_io(
+static int
+xfile_io_async(
+	struct file	*filp,
+	unsigned int	cmd_flags,
+	loff_t		*pos,
+	void		*ptr,
+	size_t		count)
+{
+	DECLARE_COMPLETION_ONSTACK(done);
+	struct xfile_io_args	args = {
+		.filp = filp,
+		.ptr = ptr,
+		.pos = pos,
+		.count = count,
+		.done = &done,
+		.is_read = (cmd_flags & XFILE_IO_MASK) == XFILE_IO_READ,
+	};
+
+	INIT_WORK_ONSTACK(&args.work, xfile_io_worker);
+	schedule_work(&args.work);
+	wait_for_completion(&done);
+	destroy_work_on_stack(&args.work);
+
+	/*
+	 * Since we're treating this file as "memory", any IO error should be
+	 * treated as a failure to find any memory.
+	 */
+	return args.ret == count ? 0 : -ENOMEM;
+}
+
+/* Perform a read or write IO to the file backing the array. */
+static int
+xfile_io_sync(
 	struct file	*filp,
 	unsigned int	cmd_flags,
 	loff_t		*pos,
@@ -71,6 +133,20 @@ xfile_io(
 	return ret == count ? 0 : -ENOMEM;
 }
 
+/* Perform a read or write IO to the file backing the array. */
+int
+xfile_io(
+	struct file	*filp,
+	unsigned int	cmd_flags,
+	loff_t		*pos,
+	void		*ptr,
+	size_t		count)
+{
+	if (cmd_flags & XFILE_IO_ASYNC)
+		return xfile_io_async(filp, cmd_flags, pos, ptr, count);
+	return xfile_io_sync(filp, cmd_flags, pos, ptr, count);
+}
+
 /* Discard pages backing a range of the file. */
 void
 xfile_discard(
diff --git a/fs/xfs/scrub/xfile.h b/fs/xfs/scrub/xfile.h
index 41817bcadc43..ae52053bf2e3 100644
--- a/fs/xfs/scrub/xfile.h
+++ b/fs/xfs/scrub/xfile.h
@@ -13,6 +13,7 @@ void xfile_destroy(struct file *filp);
 #define XFILE_IO_READ		(0)
 #define XFILE_IO_WRITE		(1)
 #define XFILE_IO_MASK		(1 << 0)
+#define XFILE_IO_ASYNC		(1 << 1)
 int xfile_io(struct file *filp, unsigned int cmd_flags, loff_t *pos,
 		void *ptr, size_t count);
 
