diff --git a/fs/ext4/Kconfig b/fs/ext4/Kconfig
index 86699c8cab28..2c01c9b335c3 100644
--- a/fs/ext4/Kconfig
+++ b/fs/ext4/Kconfig
@@ -101,6 +101,15 @@ config EXT4_DEBUG
 	  If you select Y here, then you will be able to turn on debugging
 	  using dynamic debug control for mb_debug() / ext_debug() msgs.
 
+config EXT4_FAULT_INJECTION
+	bool "Ext4 fault injection support"
+	depends on EXT4_DEBUG && FAULT_INJECTION_DEBUG_FS
+	help
+	  Enables fault injecton facility. Allow test ext4 by injecting
+	  failures like checksum error, EIO, etc. The injection could be
+	  filtered by block group, inode, logical block of file, pyhsical
+	  block, and so on.
+
 config EXT4_KUNIT_TESTS
 	tristate "KUnit tests for ext4" if !KUNIT_ALL_TESTS
 	depends on EXT4_FS && KUNIT
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 53099ffe307f..7a030b0b51c7 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -37,6 +37,7 @@
 #include <linux/falloc.h>
 #include <linux/percpu-rwsem.h>
 #include <linux/fiemap.h>
+#include <linux/fault-inject.h>
 #ifdef __KERNEL__
 #include <linux/compat.h>
 #endif
@@ -1504,6 +1505,100 @@ struct ext4_orphan_info {
 						 * file blocks */
 };
 
+#ifdef CONFIG_EXT4_FAULT_INJECTION
+#define FAULT_NOTSET	(U64_MAX)
+
+enum ext4_fault_bits {
+	EXT4_FAULT_MAX
+};
+
+struct ext4_fault_attr {
+	struct fault_attr fa_attr;
+	struct dentry *fa_dir;
+	/* filter config */
+	u64 fa_group;			/* group number */
+	u64 fa_ino;			/* inode number */
+	u64 fa_lblock;			/* logical block number */
+	u64 fa_pblock;			/* pyhsical block number */
+	/* inject fault operations bitmap */
+	DECLARE_BITMAP(fail_ops, EXT4_FAULT_MAX);
+};
+
+extern void ext4_init_fault_inject(struct super_block *sb);
+extern bool ext4_should_fail(struct super_block *sb, unsigned int bit,
+			     u64 group, u64 ino, u64 lblock, u64 pblock);
+
+#define EXT4_FAULT_FN(bit, name, errno)						\
+static inline int ext4_fault_##name(struct super_block *sb)			\
+{										\
+	bool ret = ext4_should_fail(sb, EXT4_FAULT_##bit, FAULT_NOTSET,		\
+				    FAULT_NOTSET, FAULT_NOTSET, FAULT_NOTSET);	\
+	return (ret && errno) ? (int)errno : (int)ret;				\
+}
+#define EXT4_FAULT_GRP_FN(bit, name, errno)					\
+static inline int ext4_fault_##name(struct super_block *sb, ext4_group_t group)	\
+{										\
+	bool ret = ext4_should_fail(sb, EXT4_FAULT_##bit, group,		\
+				    FAULT_NOTSET, FAULT_NOTSET, FAULT_NOTSET);	\
+	return (ret && errno) ? (int)errno : (int)ret;				\
+}
+#define EXT4_FAULT_INODE_FN(bit, name, errno)					\
+static inline int ext4_fault_##name(struct super_block *sb, unsigned long ino)	\
+{										\
+	bool ret = ext4_should_fail(sb, EXT4_FAULT_##bit, FAULT_NOTSET,		\
+				    ino ? : FAULT_NOTSET, FAULT_NOTSET,		\
+				    FAULT_NOTSET);				\
+	return (ret && errno) ? (int)errno : (int)ret;				\
+}
+#define EXT4_FAULT_INODE_LBLOCK_FN(bit, name, errno)				\
+static inline int ext4_fault_##name(struct inode *inode, ext4_lblk_t lblock)	\
+{										\
+	bool ret = ext4_should_fail(inode->i_sb, EXT4_FAULT_##bit, FAULT_NOTSET,\
+				    inode->i_ino, lblock, FAULT_NOTSET);	\
+	return (ret && errno) ? (int)errno : (int)ret;				\
+}
+#define EXT4_FAULT_INODE_PBLOCK_FN(bit, name, errno)				\
+static inline int ext4_fault_##name(struct super_block *sb, unsigned long ino,	\
+				    ext4_fsblk_t pblock)			\
+{										\
+	bool ret = ext4_should_fail(sb, EXT4_FAULT_##bit, FAULT_NOTSET,		\
+				    ino ? : FAULT_NOTSET, FAULT_NOTSET, pblock);\
+	return (ret && errno) ? (int)errno : (int)ret;				\
+}
+
+#else
+static inline void ext4_init_fault_inject(struct super_block *sb)
+{
+}
+#define EXT4_FAULT_FN(bit, name, errno)						\
+static inline int ext4_fault_##name(struct super_block *sb)			\
+{										\
+	return 0;								\
+}
+#define EXT4_FAULT_GRP_FN(bit, name, errno)					\
+static inline int ext4_fault_##name(struct super_block *sb, ext4_group_t group)	\
+{										\
+	return 0;								\
+}
+#define EXT4_FAULT_INODE_FN(bit, name, errno)					\
+static inline int ext4_fault_##name(struct super_block *sb, unsigned long ino)	\
+{										\
+	return 0;								\
+}
+#define EXT4_FAULT_INODE_LBLOCK_FN(bit, name, errno)				\
+static inline int ext4_fault_##name(struct inode *inode, ext4_lblk_t lblock)	\
+{										\
+	return 0;								\
+}
+#define EXT4_FAULT_INODE_PBLOCK_FN(bit, name, errno)				\
+static inline int ext4_fault_##name(struct super_block *sb, unsigned long ino,	\
+				    ext4_fsblk_t pblock)			\
+{										\
+	return 0;								\
+}
+
+#endif /* CONFIG_EXT4_FAULT_INJECTION */
+
 /*
  * fourth extended-fs super-block data in memory
  */
@@ -1710,6 +1805,9 @@ struct ext4_sb_info {
 	u64 s_dax_part_off;
 #ifdef CONFIG_EXT4_DEBUG
 	unsigned long s_simulate_fail;
+#endif
+#ifdef CONFIG_EXT4_FAULT_INJECTION
+	struct ext4_fault_attr s_fault_attr;
 #endif
 	/* Record the errseq of the backing block device */
 	errseq_t s_bdev_wb_err;
diff --git a/fs/ext4/sysfs.c b/fs/ext4/sysfs.c
index f3e4049ec50e..a400b2164b10 100644
--- a/fs/ext4/sysfs.c
+++ b/fs/ext4/sysfs.c
@@ -553,6 +553,8 @@ int ext4_register_sysfs(struct super_block *sb)
 	}
 	if (ext4_debugfs_root)
 		sbi->s_debug = debugfs_create_dir(sb->s_id, ext4_debugfs_root);
+	if (sbi->s_debug)
+		ext4_init_fault_inject(sb);
 	return 0;
 }
 
@@ -566,6 +568,152 @@ void ext4_unregister_sysfs(struct super_block *sb)
 	kobject_del(&sbi->s_kobj);
 }
 
+#ifdef CONFIG_EXT4_FAULT_INJECTION
+char *ext4_fault_names[EXT4_FAULT_MAX] = {
+	/* empty */
+};
+
+static int ext4_fault_available_show(struct seq_file *m, void *v)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ext4_fault_names); i++)
+		seq_printf(m, "%s\n", ext4_fault_names[i]);
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ext4_fault_available);
+
+static int ext4_fault_ops_show(struct seq_file *m, void *v)
+{
+	struct super_block *sb = m->private;
+	struct ext4_fault_attr *attr = &EXT4_SB(sb)->s_fault_attr;
+	int bit = 0;
+
+	for_each_set_bit(bit, attr->fail_ops, EXT4_FAULT_MAX)
+		seq_printf(m, "%s\n", ext4_fault_names[bit]);
+
+	return 0;
+}
+
+static int ext4_fault_ops_open(struct inode *inode, struct file *file)
+{
+	struct super_block *sb = inode->i_private;
+	struct ext4_fault_attr *attr = &EXT4_SB(sb)->s_fault_attr;
+	int ret;
+
+	ret = single_open(file, ext4_fault_ops_show, sb);
+	if (ret)
+		return ret;
+
+	if (file->f_flags & O_TRUNC)
+		bitmap_zero(attr->fail_ops, EXT4_FAULT_MAX);
+	return ret;
+}
+
+static int ext4_fault_ops_release(struct inode *inode, struct file *file)
+{
+	return single_release(inode, file);
+}
+
+static ssize_t ext4_fault_ops_write(struct file *file, const char __user *buffer,
+				    size_t count, loff_t *ppos)
+{
+	struct seq_file *m = file->private_data;
+	struct super_block *sb = m->private;
+	struct ext4_fault_attr *attr = &EXT4_SB(sb)->s_fault_attr;
+	char fault_buf[32] = { };
+	char *fault_op;
+	int i;
+
+	if (count >= sizeof(fault_buf)) {
+		ext4_msg(sb, KERN_ERR, "fault operation too long %zu", count);
+		return -EINVAL;
+	}
+	if (copy_from_user(fault_buf, buffer, count))
+		return -EFAULT;
+
+	fault_op = strstrip(fault_buf);
+	for (i = 0; i < ARRAY_SIZE(ext4_fault_names); i++) {
+		if (!strcmp(fault_op, ext4_fault_names[i])) {
+			__set_bit(i, attr->fail_ops);
+			break;
+		}
+	}
+	*ppos += count;
+	return count;
+}
+
+static const struct file_operations ext4_fault_ops_fops = {
+	.open = ext4_fault_ops_open,
+	.read = seq_read,
+	.write = ext4_fault_ops_write,
+	.llseek = seq_lseek,
+	.release = ext4_fault_ops_release,
+};
+
+
+/*
+ * Inject fault injection for one operation, it could be filtered by the
+ * group, inode, logical block and physical block. Return true if we should
+ * inject fault.
+ */
+bool ext4_should_fail(struct super_block *sb, unsigned int bit,
+		      u64 group, u64 ino, u64 lblock, u64 pblock)
+{
+	struct ext4_sb_info *sbi = EXT4_SB(sb);
+	struct ext4_fault_attr *attr = &sbi->s_fault_attr;
+
+	if (!test_bit(bit, attr->fail_ops))
+		return false;
+
+#define EXT4_FAIL_FILTER_MATCH(conf, check)		\
+	((conf == FAULT_NOTSET) || (check == FAULT_NOTSET) || (conf == check))
+
+	if (!EXT4_FAIL_FILTER_MATCH(attr->fa_group, group))
+		return false;
+	if (!EXT4_FAIL_FILTER_MATCH(attr->fa_ino, ino))
+		return false;
+	if (!EXT4_FAIL_FILTER_MATCH(attr->fa_lblock, lblock))
+		return false;
+	if (!EXT4_FAIL_FILTER_MATCH(attr->fa_pblock, pblock))
+		return false;
+
+	return should_fail(&attr->fa_attr, 1);
+}
+
+void ext4_init_fault_inject(struct super_block *sb)
+{
+	struct ext4_sb_info *sbi = EXT4_SB(sb);
+	struct ext4_fault_attr *attr = &sbi->s_fault_attr;
+	struct dentry *parent = sbi->s_debug;
+	struct dentry *dir;
+
+	attr->fa_attr = (struct fault_attr) FAULT_ATTR_INITIALIZER;
+	attr->fa_ino = FAULT_NOTSET;
+	attr->fa_group = FAULT_NOTSET;
+	attr->fa_lblock = FAULT_NOTSET;
+	attr->fa_pblock = FAULT_NOTSET;
+	memset(attr->fail_ops, 0, sizeof(attr->fail_ops));
+
+	dir = fault_create_debugfs_attr("fault_inject", parent, &attr->fa_attr);
+	if (IS_ERR(dir)) {
+		ext4_msg(sb, KERN_ERR, "failed to initialize fault_injection %ld",
+			 PTR_ERR(dir));
+		return;
+	}
+	attr->fa_dir = dir;
+	debugfs_create_file("available_faults", 0400, dir, sb,
+			    &ext4_fault_available_fops);
+	debugfs_create_file("inject_faults", 0600, dir, sb,
+			    &ext4_fault_ops_fops);
+	debugfs_create_x64("inject_inode", 0600, dir, &attr->fa_ino);
+	debugfs_create_x64("inject_group", 0600, dir, &attr->fa_group);
+	debugfs_create_x64("inject_logical_block", 0600, dir, &attr->fa_lblock);
+	debugfs_create_x64("inject_physical_block", 0600, dir, &attr->fa_pblock);
+}
+#endif /* CONFIG_EXT4_FAULT_INJECTION */
+
 int __init ext4_init_sysfs(void)
 {
 	int ret;
