[f2fs-dev] [RFC PATCH v2 2/5] f2fs: introduce inline extent mapping for inode data blocks

Yongpeng Yang <[email protected]>
Newsgroups net.sourceforge.lists.linux-f2fs-devel
Message-ID <[email protected]>
From: Yongpeng Yang <[email protected]>

Implement an inline extent mapping mechanism that stores extent
information directly in the inode's data block address area. This
provides a more space-efficient representation when contiguous block
ranges can be described by fewer extent entries than individual block
addresses.

The inline extent format uses a magic-number-based identification in the
inode block address space and maintains a sorted array of extents with
binary search for lookup operations.

Main changes:
- Add extent lookup, insert, update, and merge operations
- Add extent management (format conversion between direct blocks and
  inline extents, truncation)
- Add f2fs_iext_info to sbi for global inline extent state
- Add Kconfig options (CONFIG_F2FS_INLINE_EXTENT and
  CONFIG_F2FS_INLINE_EXTENT_DEBUG for statistics)
- Integrate with existing f2fs data path, node operations, and recovery

Test setup (Xiaomi smartphone, UFS 4.0 storage, f2fs):

  echo 1 > /sys/fs/f2fs/<dev>/inline_extent_enable
  echo 'mp4' > /sys/fs/f2fs/<dev>/inline_extent_extension_list
  fio --name=test --filename=data.mp4 --rw=write:4k --bs=64M \
      --size=8G --ioengine=libaio --direct=1
  sync
  fio --name=test --filename=data.mp4 --rw=write --bs=64M \
      --size=8G --ioengine=libaio --direct=1
  sync
  echo 3 > /proc/sys/vm/drop_caches
  fio --name=buffer-read --ioengine=libaio --rw=randread --bs=$BS \
      --size=8G --io_size=1G --numjobs=1 --filename=data.mp4

Results (random read bandwidth, MiB/s):
+---------------------------------------------------+
| BS     | baseline | inline ext | improvement      |
|--------+----------+------------+------------------|
| 4K     | 35       | 36         | +2.5%            |
| 8K     | 60       | 62         | +3%              |
| 32K    | 179      | 191        | +6.8%            |
| 64K    | 284      | 321        | +13%             |
+---------------------------------------------------+

Signed-off-by: Yongpeng Yang <[email protected]>
---
v2:
- Bypass inline extent lookup for F2FS_GET_BLOCK_PRECACHE.
- Unify fofs range check to "fofs >= direct_blocks".
- Remove NULL_ADDR caching support; simplify merge/split logic.
- Change f2fs_iext_convert_to_inline_extent return type to bool.
- Rename __is_extent_mergeable to __is_iextent_mergeable.
- Remove inode parameter from f2fs_iext_sanity_check.
- Reduce #ifdef nesting in node.c.
- Add complete benchmark data (4K/8K/32K/64K).
- Code style fixes.
---
 fs/f2fs/Kconfig    |  18 +
 fs/f2fs/Makefile   |   1 +
 fs/f2fs/data.c     | 157 ++++++++-
 fs/f2fs/dir.c      |   9 +
 fs/f2fs/f2fs.h     |  23 +-
 fs/f2fs/file.c     |   1 +
 fs/f2fs/iextent.c  | 823 +++++++++++++++++++++++++++++++++++++++++++++
 fs/f2fs/iextent.h  | 184 ++++++++++
 fs/f2fs/inline.c   |   7 +
 fs/f2fs/namei.c    |  48 +++
 fs/f2fs/node.c     |  66 +++-
 fs/f2fs/node.h     |   4 +
 fs/f2fs/recovery.c |  17 +
 fs/f2fs/super.c    |  13 +
 14 files changed, 1361 insertions(+), 10 deletions(-)
 create mode 100644 fs/f2fs/iextent.c
 create mode 100644 fs/f2fs/iextent.h

diff --git a/fs/f2fs/Kconfig b/fs/f2fs/Kconfig
index 5916a02fb46d..4a5d900090ee 100644
--- a/fs/f2fs/Kconfig
+++ b/fs/f2fs/Kconfig
@@ -150,3 +150,21 @@ config F2FS_UNFAIR_RWSEM
 	help
 	  Use unfair rw_semaphore, if system configured IO priority by block
 	  cgroup.
+
+config F2FS_INLINE_EXTENT
+	bool "F2FS inline extent"
+	depends on F2FS_FS
+	default y
+	help
+	  Support the inline extent feature: leverage the inode's data block
+	  address area to store extent-format mapping relationships, replacing
+	  individual block addresses with compact extent entries to optimize
+	  large file random reads.
+
+config F2FS_INLINE_EXTENT_DEBUG
+	bool "F2FS inline extent debug"
+	depends on F2FS_INLINE_EXTENT
+	default n
+	help
+	  Support inline extent debug to stat code coverage and extents
+	  consistency check.
diff --git a/fs/f2fs/Makefile b/fs/f2fs/Makefile
index 8a7322d229e4..ed75c0b71a93 100644
--- a/fs/f2fs/Makefile
+++ b/fs/f2fs/Makefile
@@ -4,6 +4,7 @@ obj-$(CONFIG_F2FS_FS) += f2fs.o
 f2fs-y		:= dir.o file.o inode.o namei.o hash.o super.o inline.o
 f2fs-y		+= checkpoint.o gc.o data.o node.o segment.o recovery.o
 f2fs-y		+= shrinker.o extent_cache.o sysfs.o
+f2fs-$(CONFIG_F2FS_INLINE_EXTENT) += iextent.o
 f2fs-$(CONFIG_F2FS_STAT_FS) += debug.o
 f2fs-$(CONFIG_F2FS_FS_XATTR) += xattr.o
 f2fs-$(CONFIG_F2FS_FS_POSIX_ACL) += acl.o
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 9c6440a7db0e..2a98626051fb 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -26,6 +26,7 @@
 #include "node.h"
 #include "segment.h"
 #include "iostat.h"
+#include "iextent.h"
 #include <trace/events/f2fs.h>
 
 #define NUM_PREALLOC_POST_READ_CTXS	128
@@ -1203,11 +1204,66 @@ static void f2fs_submit_page_read(struct inode *inode, struct fsverity_info *vi,
 	f2fs_submit_read_bio(sbi, bio, DATA);
 }
 
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+static bool __set_iext_data_blkaddr(struct dnode_of_data *dn, block_t blkaddr)
+{
+	block_t fofs = f2fs_start_bidx_of_node(ofs_of_node(dn->node_folio),
+						dn->inode) + dn->ofs_in_node;
+	int ret;
+	bool need_update = false;
+
+	/*
+	 * Don't cache extent during recovery, be consistent with largest
+	 * extent.
+	 */
+	if (unlikely(is_sbi_flag_set(F2FS_I_SB(dn->inode), SBI_POR_DOING)) &&
+			fofs >= ADDRS_PER_INODE(dn->inode)) {
+		f2fs_bug_on(F2FS_I_SB(dn->inode),
+			f2fs_iext_last_fofs(dn->inode, dn->inode_folio) >=
+			ADDRS_PER_INODE(dn->inode));
+		return need_update;
+	}
+
+	f2fs_folio_wait_writeback(dn->inode_folio, NODE, true, true);
+	ret = f2fs_iext_update_data_blkaddr(dn->inode, dn->inode_folio, fofs,
+						blkaddr);
+	switch (ret) {
+	case F2FS_IEXT_INSERT_CONVERT:
+		/* caller need to update direct blocks array. */
+		f2fs_iext_convert_to_direct_blocks(dn->inode, dn->inode_folio);
+		need_update = true;
+		fallthrough;
+	case F2FS_IEXT_INSERT_REMOVED:
+	case F2FS_IEXT_INSERT_NORMAL:
+		if (folio_mark_dirty(dn->inode_folio))
+			dn->node_changed = true;
+		break;
+	case F2FS_IEXT_INSERT_DROP:
+		break;
+	default:
+		f2fs_bug_on(F2FS_I_SB(dn->inode), 1);
+	}
+	return need_update;
+}
+#endif
+
 static void __set_data_blkaddr(struct dnode_of_data *dn, block_t blkaddr)
 {
 	__le32 *addr = get_dnode_addr(dn->inode, dn->node_folio);
 
 	dn->data_blkaddr = blkaddr;
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+	if (f2fs_iext_support_inline_extent(dn->inode,
+			dn->inode_folio)) {
+		f2fs_bug_on(F2FS_I_SB(dn->inode),
+				!dn->inode_folio_locked);
+		f2fs_bug_on(F2FS_I_SB(dn->inode),
+				!folio_test_locked(dn->inode_folio));
+		if (!__set_iext_data_blkaddr(dn, blkaddr) &&
+				dn->node_folio == dn->inode_folio)
+			return;
+	}
+#endif
 	addr[dn->ofs_in_node] = cpu_to_le32(dn->data_blkaddr);
 }
 
@@ -1645,6 +1701,61 @@ static bool map_is_mergeable(struct f2fs_sb_info *sbi,
 	return false;
 }
 
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+static bool f2fs_iext_map_blocks(struct inode *inode,
+		struct f2fs_map_blocks *map, int flag)
+{
+	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+	unsigned int maxblocks = map->m_len;
+	pgoff_t pgoff = (pgoff_t)map->m_lblk;
+	block_t blkaddr;
+	struct folio *ifolio;
+	unsigned int len;
+	int ret;
+
+	if (f2fs_compressed_file(inode))
+		return false;
+
+	ifolio = f2fs_get_inode_folio(sbi, inode->i_ino);
+	if (IS_ERR(ifolio))
+		return false;
+	if (!f2fs_iext_support_inline_extent(inode, ifolio)) {
+		f2fs_folio_put(ifolio, true);
+		return false;
+	}
+	ret = f2fs_iext_lookup_blkaddr(inode, ifolio, pgoff, &blkaddr, &len);
+	f2fs_folio_put(ifolio, true);
+	if (ret)
+		return false;
+
+	if (blkaddr == NULL_ADDR || blkaddr == NEW_ADDR)
+		return false;
+	map->m_pblk = blkaddr;
+	map->m_len = min_t(unsigned int, maxblocks, len);
+	map->m_flags = F2FS_MAP_MAPPED;
+	if (map->m_next_extent)
+		*map->m_next_extent = pgoff + map->m_len;
+
+	/* for hardware encryption, but to avoid potential issue in future */
+	if (flag == F2FS_GET_BLOCK_DIO)
+		f2fs_wait_on_block_writeback_range(inode,
+					map->m_pblk, map->m_len);
+
+	map->m_multidev_dio = f2fs_allow_multi_device_dio(sbi, flag);
+	if (map->m_multidev_dio) {
+		int bidx = f2fs_target_device_index(sbi, map->m_pblk);
+		struct f2fs_dev_info *dev = &sbi->devs[bidx];
+
+		map->m_bdev = dev->bdev;
+		map->m_len = min(map->m_len, dev->end_blk + 1 - map->m_pblk);
+		map->m_pblk -= dev->start_blk;
+	} else {
+		map->m_bdev = inode->i_sb->s_bdev;
+	}
+	return true;
+}
+#endif
+
 /*
  * f2fs_map_blocks() tries to find or build mapping relationship which
  * maps continuous logical blocks to physical blocks, and return such
@@ -1694,6 +1805,23 @@ int f2fs_map_blocks(struct inode *inode, struct f2fs_map_blocks *map, int flag)
 		goto map_more;
 	}
 
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+	/*
+	 * Precache need to load all mapping to read extent cache, so we need
+	 * to bypass inline extent.
+	 */
+	if (!map->m_may_create && flag != F2FS_GET_BLOCK_PRECACHE &&
+			f2fs_iext_map_blocks(inode, map, flag)) {
+		if (map->m_len == maxblocks ||
+			map->m_multidev_dio ||
+			flag != F2FS_GET_BLOCK_FIEMAP)
+			goto out;
+		pgofs =	(pgoff_t)map->m_lblk + map->m_len;
+		ofs = map->m_len;
+		goto map_more;
+	}
+#endif
+
 	map->m_bdev = inode->i_sb->s_bdev;
 	map->m_multidev_dio =
 		f2fs_allow_multi_device_dio(F2FS_I_SB(inode), flag);
@@ -3706,6 +3834,31 @@ void f2fs_write_failed(struct inode *inode, loff_t to)
 	}
 }
 
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+static bool f2fs_iext_get_data_blkaddr(struct inode *inode,
+			struct folio *ifolio, pgoff_t index, block_t *blkaddr)
+{
+	int ret;
+
+	if (f2fs_compressed_file(inode))
+		return false;
+	if (!f2fs_iext_support_inline_extent(inode, ifolio))
+		return false;
+
+	ret = f2fs_iext_lookup_blkaddr(inode, ifolio, index, blkaddr, NULL);
+	if (ret || *blkaddr == NULL_ADDR || *blkaddr == NEW_ADDR)
+		return false;
+
+	return true;
+}
+#else
+static bool f2fs_iext_get_data_blkaddr(struct inode *inode,
+			struct folio *ifolio, pgoff_t index, block_t *blkaddr)
+{
+	return false;
+}
+#endif
+
 static int prepare_write_begin(struct f2fs_sb_info *sbi,
 			struct folio *folio, loff_t pos, unsigned int len,
 			block_t *blk_addr, bool *node_changed)
@@ -3761,7 +3914,9 @@ static int prepare_write_begin(struct f2fs_sb_info *sbi,
 	}
 
 	if (!f2fs_lookup_read_extent_cache_block(inode, index,
-						 &dn.data_blkaddr)) {
+				&dn.data_blkaddr) &&
+			!f2fs_iext_get_data_blkaddr(inode, ifolio, index,
+				&dn.data_blkaddr)) {
 		if (IS_DEVICE_ALIASING(inode)) {
 			err = -ENODATA;
 			goto out;
diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c
index b1697194c3c4..47c8d24961a3 100644
--- a/fs/f2fs/dir.c
+++ b/fs/f2fs/dir.c
@@ -16,6 +16,7 @@
 #include "node.h"
 #include "acl.h"
 #include "xattr.h"
+#include "iextent.h"
 #include <trace/events/f2fs.h>
 
 static inline bool f2fs_should_fallback_to_linear(struct inode *dir)
@@ -543,6 +544,14 @@ struct folio *f2fs_init_inode_metadata(struct inode *inode, struct inode *dir,
 		if (IS_ERR(folio))
 			return folio;
 
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+		if (S_ISREG(inode->i_mode) &&
+				(F2FS_I(inode)->i_flags & F2FS_EXTENT_FL) &&
+				!is_inode_flag_set(inode, FI_INLINE_DATA)) {
+			f2fs_iext_init_inline_extent(inode, folio);
+			folio_mark_dirty(folio);
+		}
+#endif
 		if (S_ISDIR(inode->i_mode)) {
 			/* in order to handle error case */
 			folio_get(folio);
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index fffb516b78f4..e1402ee09c5e 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -1137,6 +1137,8 @@ static inline void set_new_dnode(struct dnode_of_data *dn, struct inode *inode,
 	dn->inode_folio = ifolio;
 	dn->node_folio = nfolio;
 	dn->nid = nid;
+	if (ifolio != NULL)
+		dn->inode_folio_locked = folio_test_locked(ifolio);
 }
 
 /*
@@ -1769,6 +1771,10 @@ struct f2fs_sb_info {
 	struct f2fs_nm_info *nm_info;		/* node manager */
 	struct inode *node_inode;		/* cache node blocks */
 
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+	struct f2fs_iext_info *iext_info;
+#endif
+
 	/* for segment-related operations */
 	struct f2fs_sm_info *sm_info;		/* segment manager */
 
@@ -3106,12 +3112,15 @@ static inline void f2fs_put_page(struct page *page, bool unlock)
 	f2fs_folio_put(page_folio(page), unlock);
 }
 
+static inline __le32 *get_dnode_addr(struct inode *inode,
+					struct folio *node_folio);
 static inline void f2fs_put_dnode(struct dnode_of_data *dn)
 {
 	if (dn->node_folio)
 		f2fs_folio_put(dn->node_folio, true);
 	if (dn->inode_folio && dn->node_folio != dn->inode_folio)
-		f2fs_folio_put(dn->inode_folio, false);
+		f2fs_folio_put(dn->inode_folio,
+				dn->inode_folio_locked);
 	dn->node_folio = NULL;
 	dn->inode_folio = NULL;
 }
@@ -3240,9 +3249,20 @@ static inline __le32 *get_dnode_addr(struct inode *inode,
 			get_dnode_base(inode, node_folio);
 }
 
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+int f2fs_iext_data_blkaddr(struct inode *inode,
+			struct folio *node_folio, unsigned int offset,
+			block_t *blkaddr);
+#endif
 static inline block_t data_blkaddr(struct inode *inode,
 			struct folio *node_folio, unsigned int offset)
 {
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+	block_t blkaddr;
+
+	if (!f2fs_iext_data_blkaddr(inode, node_folio, offset, &blkaddr))
+		return blkaddr;
+#endif
 	return le32_to_cpu(*(get_dnode_addr(inode, node_folio) + offset));
 }
 
@@ -3323,6 +3343,7 @@ static inline void f2fs_change_bit(unsigned int nr, char *addr)
 #define F2FS_NOCOMP_FL			0x00000400 /* Don't compress */
 #define F2FS_INDEX_FL			0x00001000 /* hash-indexed directory */
 #define F2FS_DIRSYNC_FL			0x00010000 /* dirsync behaviour (directories only) */
+#define F2FS_EXTENT_FL			0x00080000 /* Extents */
 #define F2FS_PROJINHERIT_FL		0x20000000 /* Create with parents projid */
 #define F2FS_CASEFOLD_FL		0x40000000 /* Casefolded file */
 #define F2FS_DEVICE_ALIAS_FL		0x80000000 /* File for aliasing a device */
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index e40e136f9d43..7db9cef5bc65 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -2209,6 +2209,7 @@ static const struct {
 	{ F2FS_NOCOMP_FL,	FS_NOCOMP_FL },
 	{ F2FS_INDEX_FL,	FS_INDEX_FL },
 	{ F2FS_DIRSYNC_FL,	FS_DIRSYNC_FL },
+	{ F2FS_EXTENT_FL,	FS_EXTENT_FL },
 	{ F2FS_PROJINHERIT_FL,	FS_PROJINHERIT_FL },
 	{ F2FS_CASEFOLD_FL,	FS_CASEFOLD_FL },
 };
diff --git a/fs/f2fs/iextent.c b/fs/f2fs/iextent.c
new file mode 100644
index 000000000000..d919448ef740
--- /dev/null
+++ b/fs/f2fs/iextent.c
@@ -0,0 +1,823 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * fs/f2fs/iextent.c
+ *
+ * Copyright (c) 2026 Xiaomi Technology Co., Ltd.
+ *             http://www.mi.com/
+ */
+#include <linux/f2fs_fs.h>
+#include <linux/seq_file.h>
+
+#include "f2fs.h"
+#include "iextent.h"
+
+/*
+ * ASSERT - debug assertion for inline extent code.
+ * Uses f2fs_bug_on when sbi is available, falls back to WARN_ON_ONCE otherwise.
+ */
+#define ASSERT(sbi, condition) do {			\
+	if (!(sbi))					\
+		WARN_ON_ONCE(!(condition));		\
+	else						\
+		f2fs_bug_on((sbi), !(condition));	\
+} while (0)
+
+bool f2fs_iext_sanity_check(struct folio *ifolio)
+{
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)get_dnode_addr(NULL, ifolio);
+	struct f2fs_extent *ix;
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+	int k;
+
+	if (!S_ISREG(le16_to_cpu(F2FS_NODE(ifolio)->i.i_mode)))
+		return false;
+
+	if (inline_extents == 0)
+		return true;
+
+	ix = EXT_FIRST_INDEX(eh);
+	for (k = 0; k < EXT_ENTRY_COUNT(eh); k++, ix++) {
+		if (F2FS_EXT_LEN(ix) == 0)
+			return false;
+		if (F2FS_EXT_LOGICAL_START(ix) > UINT_MAX - F2FS_EXT_LEN(ix))
+			return false;
+		if (F2FS_EXT_PHYSICAL_START(ix) != NEW_ADDR &&
+			F2FS_EXT_PHYSICAL_START(ix) >
+			UINT_MAX - F2FS_EXT_LEN(ix))
+			return false;
+		if (k == 0)
+			continue;
+		if (F2FS_EXT_LOGICAL_START(ix) <=
+				F2FS_EXT_LOGICAL_END(&ix[-1]))
+			return false;
+	}
+	return true;
+}
+
+/*
+ * Borrowed from ext4_ext_binsearch_idx.
+ *
+ * __iext_binsearch_idx:
+ * binary search for the closest index of the given block
+ * the header must be checked before calling this
+ */
+static int __iext_binsearch_idx(struct inode *inode, struct folio *ifolio,
+			block_t block)
+{
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)get_dnode_addr(inode, ifolio);
+	struct f2fs_extent *r, *l, *m;
+	int index = 0;
+
+	if (EXT_ENTRY_COUNT(eh) == 0)
+		return -1;
+
+	l = EXT_FIRST_INDEX(eh) + 1;
+	r = EXT_LAST_INDEX(eh);
+	while (l <= r) {
+		m = l + (r - l) / 2;
+		if (block < le32_to_cpu(m->fofs))
+			r = m - 1;
+		else
+			l = m + 1;
+	}
+
+	index = l - EXT_FIRST_INDEX(eh) - 1;
+
+#ifdef CONFIG_F2FS_INLINE_EXTENT_DEBUG
+	{
+		struct f2fs_sb_info *sbi = inode ? F2FS_I_SB(inode) : NULL;
+		struct f2fs_extent *chix, *ix;
+		int k;
+
+		chix = ix = EXT_FIRST_INDEX(eh);
+		for (k = 0; k < EXT_ENTRY_COUNT(eh); k++, ix++) {
+			if (k != 0 && le32_to_cpu(ix->fofs) <=
+			    le32_to_cpu(ix[-1].fofs)) {
+				f2fs_debug(sbi, "k=%d, ix=0x%p, first=0x%p",
+				       k, ix, EXT_FIRST_INDEX(eh));
+				f2fs_debug(sbi, "%u <= %u",
+				       le32_to_cpu(ix->fofs),
+				       le32_to_cpu(ix[-1].fofs));
+			}
+			ASSERT(sbi, !(k && le32_to_cpu(ix->fofs)
+					   <= le32_to_cpu(ix[-1].fofs)));
+			if (block < le32_to_cpu(ix->fofs))
+				break;
+			chix = ix;
+		}
+		ASSERT(sbi, chix == l - 1);
+	}
+#endif
+
+	return index;
+}
+
+static void __ext_add_one_entry(struct f2fs_iext_header *eh, int index,
+			struct f2fs_extent *ext)
+{
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+
+	if (WARN_ON_ONCE(index < 0 || index > inline_extents))
+		return;
+
+	memmove(&eh->exts[index + 1], &eh->exts[index],
+			(inline_extents - index) * sizeof(struct f2fs_extent));
+	memcpy(&eh->exts[index], ext, sizeof(struct f2fs_extent));
+
+	eh->cnt = cpu_to_le32(inline_extents + 1);
+}
+
+static void __ext_del_one_entry(struct f2fs_iext_header *eh, int index)
+{
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+
+	memmove(&eh->exts[index], &eh->exts[index + 1],
+			(inline_extents - index - 1) *
+			sizeof(struct f2fs_extent));
+
+	eh->cnt = cpu_to_le32(inline_extents - 1);
+}
+
+enum {
+	F2FS_EXT_HIT_LEFT = -1,
+	F2FS_EXT_HIT_MID = 0,
+	F2FS_EXT_HIT_RIGHT = 1,
+};
+
+static int __is_iextent_hit(struct f2fs_extent *ext, block_t fofs)
+{
+	block_t start = F2FS_EXT_LOGICAL_START(ext);
+	block_t end = F2FS_EXT_LOGICAL_END(ext);
+
+	if (fofs < start)
+		return F2FS_EXT_HIT_LEFT;
+	if (fofs > end)
+		return F2FS_EXT_HIT_RIGHT;
+	return F2FS_EXT_HIT_MID;
+}
+
+static bool __is_iextent_mergeable(struct f2fs_extent *left,
+			struct f2fs_extent *right)
+{
+	block_t left_blk_start = F2FS_EXT_PHYSICAL_START(left);
+	block_t right_blk_start = F2FS_EXT_PHYSICAL_START(right);
+
+	if (F2FS_EXT_LOGICAL_END(left) + 1 != F2FS_EXT_LOGICAL_START(right))
+		return false;
+
+	/* NEW_ADDR always can merge. */
+	if (left_blk_start == right_blk_start) {
+		/*
+		 * In fallocate context, FALLOC_FL_COLLAPSE_RANGE mode might
+		 * cause `left_blk_start == right_blk_start` and not equal
+		 * NEW_ADDR.
+		 */
+		if (left_blk_start == NEW_ADDR)
+			return true;
+		return false;
+	}
+	if (left_blk_start == NULL_ADDR ||
+			left_blk_start == NEW_ADDR ||
+			right_blk_start == NULL_ADDR ||
+			right_blk_start == NEW_ADDR)
+		return false;
+	if (F2FS_EXT_PHYSICAL_END(left) + 1 != F2FS_EXT_PHYSICAL_START(right))
+		return false;
+
+	return true;
+}
+
+/*
+ * 1. all single extent insert operation must check mergeable at first even for
+ * split case.
+ * 2. all -ENOSPC error will delete the old mapping except for case which will
+ * split extent into 2 separate extent and insert new one.
+ * 3. index point to 0th extent or extent which first fofs is bigger than @fofs.
+ * 4. delete old mapping before insert new one even for extent which len is 1.
+ */
+
+static int __iext_insert_idx(struct inode *inode, struct folio *ifolio,
+				block_t fofs, block_t blkaddr)
+{
+	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+	int max_inline_extents = MAX_INLINE_EXTENTS(inode);
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)get_dnode_addr(inode, ifolio);
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+	int index = 0, left = -1, right = -1, ret = 0, hit;
+	int merge_bits = 0, max_split_cnt = 0;
+	block_t fofs_start, fofs_end, blk_start;
+	unsigned int len;
+	struct f2fs_extent *last_ext;
+	struct f2fs_extent split;
+	struct f2fs_extent ext = {
+		.fofs = cpu_to_le32(fofs),
+		.blk = cpu_to_le32(blkaddr),
+		.len = cpu_to_le32(1),
+	};
+#ifdef CONFIG_F2FS_INLINE_EXTENT_DEBUG
+	struct f2fs_iext_info *iext_info = sbi->iext_info;
+#endif
+
+	ASSERT(sbi, EXT_ENTRY_COUNT(eh) <= MAX_INLINE_EXTENTS(inode));
+
+	/* 1. insert empty inline extent area. */
+	if (inline_extents == 0) {
+		ASSERT(sbi, max_inline_extents > 0);
+		INLINE_EXT_STAT_INC(iext_info, overwrite_ext_count);
+		goto add_entry;
+	}
+
+	/* 2. try merge with last extent. */
+	last_ext = EXT_LAST_INDEX(eh);
+	if (__is_iextent_mergeable(last_ext, &ext)) {
+		last_ext->len = cpu_to_le32(F2FS_EXT_LEN(last_ext) + 1);
+		INLINE_EXT_STAT_INC(iext_info, fast_right_merge_count);
+		goto out;
+	}
+
+	if (MAX_INLINE_EXTENTS(inode) == inline_extents &&
+			fofs > F2FS_EXT_LOGICAL_END(last_ext)) {
+		INLINE_EXT_STAT_INC(iext_info, drop_insert_new_ext_cnt);
+		ret = -E2BIG;
+		goto out;
+	}
+	/* 3. search extent. */
+	index = __iext_binsearch_idx(inode, ifolio, fofs);
+
+	fofs_start = F2FS_EXT_LOGICAL_START(&eh->exts[index]);
+	fofs_end = F2FS_EXT_LOGICAL_END(&eh->exts[index]);
+	blk_start = F2FS_EXT_PHYSICAL_START(&eh->exts[index]);
+	len = F2FS_EXT_LEN(&eh->exts[index]);
+
+	/* 4. check extent hit. */
+	hit = __is_iextent_hit(&eh->exts[index], fofs);
+	switch (hit) {
+	case F2FS_EXT_HIT_LEFT:
+		/* only if fofs < eh->exts[0].fofs. */
+		ASSERT(sbi, index == 0);
+		right = index;
+		break;
+	case F2FS_EXT_HIT_RIGHT:
+		left = index;
+		if (left + 1 < inline_extents)
+			right = left + 1;
+		break;
+	case F2FS_EXT_HIT_MID:
+		max_split_cnt = 0;
+
+		/* 5. overwrite extent. */
+		if (len == 1) {
+			__ext_del_one_entry(eh, index);
+			inline_extents = EXT_ENTRY_COUNT(eh);
+			/*
+			 * just overwrite for single extent, other cases may
+			 * cause merge.
+			 */
+			if (inline_extents == 0) {
+				/*
+				 * blkaddr can be NULL_ADDR, so need to check
+				 * before update.
+				 */
+				INLINE_EXT_STAT_INC(iext_info,
+						overwrite_ext_count);
+				goto add_entry;
+			}
+			if (index < inline_extents)
+				right = index;
+			if (index - 1 >= 0)
+				index = left = index - 1;
+			/*
+			 * index may not change but extent content has already
+			 * changed.
+			 */
+			fofs_start = F2FS_EXT_LOGICAL_START(&eh->exts[index]);
+			fofs_end = F2FS_EXT_LOGICAL_END(&eh->exts[index]);
+			blk_start = F2FS_EXT_PHYSICAL_START(&eh->exts[index]);
+			len = F2FS_EXT_LEN(&eh->exts[index]);
+			break;
+		}
+		/* 6. split extent. */
+		if (blk_start == NEW_ADDR && blk_start == blkaddr)
+			goto out;
+		if (blk_start != NEW_ADDR &&
+				blk_start + fofs - fofs_start == blkaddr)
+			goto out;
+		if (fofs_start == fofs || fofs_end == fofs)
+			max_split_cnt = 1;
+		else
+			max_split_cnt = 2;
+		switch (max_split_cnt) {
+		case 1:
+			/*
+			 * Corner cases:
+			 * 1. actual split count maybe 0 or 1, because @ext
+			 * might merge with another extent.
+			 * 2. truncate fofs at first even though ext can not
+			 * merge and no space, because ext will add to inline
+			 * extent area or may out of range of inline extent
+			 * area.
+			 */
+			eh->exts[index].len = cpu_to_le32(F2FS_EXT_LEN(
+						&eh->exts[index]) - 1);
+			eh->exts[index].fofs = cpu_to_le32(fofs == fofs_start ?
+						fofs_start + 1 : fofs_start);
+			if (blk_start != NEW_ADDR)
+				eh->exts[index].blk = cpu_to_le32(
+						fofs == fofs_start ?
+						blk_start + 1 : blk_start);
+			/* add ext at index or 'index + 1' */
+			if (fofs == fofs_end) {
+				INLINE_EXT_STAT_INC(iext_info,
+						split_right_count);
+				/* may be can merge with the right of index */
+				if (index + 1 < inline_extents)
+					right = index + 1;
+				else {
+					index++; /* insert to next position. */
+					goto add_entry;
+				}
+			} else {
+				INLINE_EXT_STAT_INC(iext_info,
+						split_left_count);
+				/* may be can merge with the left of index */
+				if (index - 1 >= 0)
+					index = left = index - 1;
+				else
+					goto add_entry;
+			}
+			fofs_start = F2FS_EXT_LOGICAL_START(&eh->exts[index]);
+			fofs_end = F2FS_EXT_LOGICAL_END(&eh->exts[index]);
+			blk_start = F2FS_EXT_PHYSICAL_START(&eh->exts[index]);
+			len = F2FS_EXT_LEN(&eh->exts[index]);
+			break;
+		case 2:
+			if (inline_extents + max_split_cnt >
+					max_inline_extents) {
+				ret = -ENOSPC;
+				goto out;
+			}
+			eh->exts[index].len = cpu_to_le32(fofs - fofs_start);
+
+			split.fofs = cpu_to_le32(fofs + 1);
+			split.len = cpu_to_le32(fofs_end - fofs);
+			if (blk_start != NEW_ADDR)
+				split.blk = cpu_to_le32(blk_start + fofs -
+						fofs_start + 1);
+			else
+				split.blk = cpu_to_le32(blk_start);
+			__ext_add_one_entry(eh, index + 1, &split);
+			inline_extents = EXT_ENTRY_COUNT(eh);
+			/* add ext between index and 'index + 1' */
+			index++;
+			INLINE_EXT_STAT_INC(iext_info, split_mid_count);
+			goto add_entry;
+		default:
+			ASSERT(sbi, 0);
+		}
+		break;
+	default:
+		ASSERT(sbi, 0);
+	}
+	/* 7. try to merge extent. */
+	if (left >= 0 && __is_iextent_mergeable(&eh->exts[left], &ext))
+		merge_bits |= 1;
+	if (right >= 0 && __is_iextent_mergeable(&ext, &eh->exts[right]))
+		merge_bits |= (1 << 1);
+	switch (merge_bits) {
+	case 1:
+		eh->exts[left].len = cpu_to_le32(F2FS_EXT_LEN(
+					&eh->exts[left]) + 1);
+		INLINE_EXT_STAT_INC(iext_info, right_merge_count);
+		goto out;
+	case 2:
+		eh->exts[right].len = cpu_to_le32(F2FS_EXT_LEN(
+					&eh->exts[right]) + 1);
+		eh->exts[right].fofs = cpu_to_le32(fofs);
+		eh->exts[right].blk = cpu_to_le32(blkaddr);
+		INLINE_EXT_STAT_INC(iext_info, left_merge_count);
+		goto out;
+	case 3:
+		eh->exts[left].len = cpu_to_le32(F2FS_EXT_LEN(&eh->exts[left])
+					+ 1 + F2FS_EXT_LEN(&eh->exts[right]));
+		__ext_del_one_entry(eh, right);
+		INLINE_EXT_STAT_INC(iext_info, del_ext_count);
+		INLINE_EXT_STAT_INC(iext_info, lr_merge_count);
+		goto out;
+	default:
+		/* no extent can merge. */
+		ASSERT(sbi, merge_bits == 0);
+	}
+	/* insert ext after found index. always false for split case. */
+	if (fofs > fofs_start)
+		index++;
+	INLINE_EXT_STAT_INC(iext_info, insert_new_ext_count);
+add_entry:
+	/*
+	 * Just bypass insert new NULL_ADDR, because the old blkaddr can not
+	 * be NULL_ADDR, and inserting need to delete it at first.
+	 * This will bypass all newly NULL_ADDR insert operation.
+	 */
+	if (blkaddr == NULL_ADDR) {
+		/* ret = 0; delete succeed. */
+		goto out;
+	}
+	if (inline_extents + 1 > max_inline_extents) {
+		ret = -ENOSPC;
+		goto out;
+	}
+	/* 8. insert as new extent. */
+	INLINE_EXT_STAT_INC(iext_info, add_ext_count);
+	__ext_add_one_entry(eh, index, &ext);
+out:
+	ASSERT(sbi, EXT_ENTRY_COUNT(eh) <= MAX_INLINE_EXTENTS(inode));
+	return ret;
+}
+
+/* Lookup whether fofs is exists in inline extent area. */
+static int __iext_lookup_data_blkaddr(struct inode *inode, struct folio *ifolio,
+				block_t fofs, block_t *blkaddr,
+				unsigned int *len)
+{
+	int index, hit;
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)get_dnode_addr(inode, ifolio);
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+	block_t blk_start, fofs_start;
+
+	if (inline_extents == 0)
+		return -ENOENT;
+	index = __iext_binsearch_idx(inode, ifolio, fofs);
+	hit = __is_iextent_hit(&eh->exts[index], fofs);
+	if (hit != F2FS_EXT_HIT_MID)
+		return -ENOENT;
+	blk_start = F2FS_EXT_PHYSICAL_START(&eh->exts[index]);
+	fofs_start = F2FS_EXT_LOGICAL_START(&eh->exts[index]);
+	if (blk_start == NEW_ADDR) {
+		*blkaddr = blk_start;
+		if (len)
+			*len = 1;
+	} else {
+		*blkaddr = blk_start + fofs - fofs_start;
+		if (len)
+			*len = F2FS_EXT_LEN(&eh->exts[index]) -
+					(fofs - fofs_start);
+	}
+	return 0;
+}
+
+int f2fs_iext_lookup_blkaddr(struct inode *inode, struct folio *ifolio,
+					block_t fofs, block_t *blkaddr,
+					unsigned int *len)
+{
+	int error = 0;
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)get_dnode_addr(inode, ifolio);
+	struct f2fs_extent *last_ext = EXT_LAST_INDEX(eh);
+
+	if (EXT_ENTRY_COUNT(eh) == 0 || F2FS_EXT_LOGICAL_END(last_ext) < fofs)
+		error = -ENOENT;
+	else
+		error = __iext_lookup_data_blkaddr(inode, ifolio, fofs,
+						blkaddr, len);
+
+	/* return NULL_ADDR for direct blocks. */
+	if (error && fofs < ADDRS_PER_INODE(inode)) {
+		*blkaddr = NULL_ADDR;
+		error = 0;
+		if (len)
+			*len = 1;
+	}
+	return error;
+}
+
+/*
+ * Exceptions:
+ * 1. inline extent area is full, blkaddr is overlap with EXT_LAST_INDEX()
+ *    1.1 blkaddr hit the right/left most or middle blk of EXT_LAST_INDEX()
+ *    1.2 the above cases is similar with extent merge
+ * 2. inline extent area is full, blkaddr is less than EXT_LAST_INDEX()
+ *    2.1 remove EXT_LAST_INDEX() and try again.
+ * 3. inline extent area is full, blkaddr is bigger than EXT_LAST_INDEX(), just
+ *    drop it.
+ *
+ * The above exceptions will be handled twice at most, because 1 insert
+ * operation may cause 2 extent insert operations at most.
+ *
+ * This function inform whether need to mark inode folio as dirty through return
+ * value.
+ */
+
+int f2fs_iext_update_data_blkaddr(struct inode *inode, struct folio *ifolio,
+					block_t fofs, block_t blkaddr)
+{
+	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+	int error = 0, retry_cnt = 2, hit;
+	int direct_blocks = ADDRS_PER_INODE(inode);
+	unsigned int len;
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)get_dnode_addr(inode, ifolio);
+	struct f2fs_extent *last_ext;
+	block_t fofs_start;
+#ifdef CONFIG_F2FS_INLINE_EXTENT_DEBUG
+	struct f2fs_iext_info *iext_info = sbi->iext_info;
+#endif
+
+retry:
+	ASSERT(sbi, retry_cnt >= 0);
+	ASSERT(sbi, EXT_ENTRY_COUNT(eh) <= MAX_INLINE_EXTENTS(inode));
+	error = __iext_insert_idx(inode, ifolio, fofs, blkaddr);
+
+	if (!error)
+		return 0;
+	/*
+	 * fofs doesn't exists in inline extent area and bigger than last
+	 * fofs. Caller only need to give up insert.
+	 */
+	if (error == -E2BIG && fofs >= direct_blocks)
+		return F2FS_IEXT_INSERT_DROP;
+	/* evict the last extent and try to insert again. */
+	/* must be after __iext_insert_idx, which might remove extent. */
+	last_ext = EXT_LAST_INDEX(eh);
+	ASSERT(sbi, last_ext);
+	if (!last_ext)
+		return F2FS_IEXT_INSERT_CONVERT;
+	fofs_start = F2FS_EXT_LOGICAL_START(last_ext);
+	len = F2FS_EXT_LEN(last_ext);
+	hit = __is_iextent_hit(last_ext, fofs);
+	switch (hit) {
+	case F2FS_EXT_HIT_LEFT:
+		/*
+		 * if last extent cover direct blocks, we must interrupt and
+		 * trigger format conversion.
+		 */
+		if (fofs_start < direct_blocks) {
+			error = F2FS_IEXT_INSERT_CONVERT;
+			INLINE_EXT_STAT_INC(iext_info,
+					trigger_ext_format_convert);
+			goto out;
+		}
+		/*
+		 * whether fofs exceed ADDRS_PER_INODE or not, just delete the
+		 * lastest extent and try again.
+		 */
+		INLINE_EXT_STAT_INC(iext_info, evict_last_ext_cnt);
+		__ext_del_one_entry(eh, EXT_ENTRY_COUNT(eh) - 1);
+		break;
+	case F2FS_EXT_HIT_RIGHT:
+		/*
+		 * don't need to cache it in inline extent area and old mapping
+		 * has already removed.
+		 */
+		if (fofs >= direct_blocks) {
+			INLINE_EXT_STAT_INC(iext_info, drop_insert_new_ext_cnt);
+			error = F2FS_IEXT_INSERT_REMOVED;
+			goto out;
+		}
+		INLINE_EXT_STAT_INC(iext_info, trigger_ext_format_convert);
+		error = F2FS_IEXT_INSERT_CONVERT;
+		goto out;
+	case F2FS_EXT_HIT_MID:
+		/*
+		 * if `len == 1` then {fofs, blkaddr, 1} already overwrite old
+		 * extent.
+		 */
+		ASSERT(sbi, len > 1);
+		/*
+		 * truncate [fofs + 1, fofs_start + len - 1] and replace
+		 * last_ext with {fofs, blkaddr, 1}.
+		 * `fofs == fofs_start` will never happen, because
+		 * __iext_insert_idx will truncate fofs.
+		 *
+		 * truncate [fofs, fofs_start + len - 1] when fofs are out of
+		 * range of [0, direct_blocks).
+		 */
+		if (fofs > fofs_start && fofs >= direct_blocks) {
+			last_ext->len = cpu_to_le32(fofs - fofs_start);
+			INLINE_EXT_STAT_INC(iext_info, truncate_last_ext_cnt);
+			if (EXT_ENTRY_COUNT(eh) == MAX_INLINE_EXTENTS(inode)) {
+				error = F2FS_IEXT_INSERT_REMOVED;
+				goto out;
+			}
+		} else {
+			error = F2FS_IEXT_INSERT_CONVERT;
+			goto out;
+		}
+		break;
+	}
+	ASSERT(sbi, EXT_ENTRY_COUNT(eh) <= MAX_INLINE_EXTENTS(inode));
+	retry_cnt--;
+	goto retry;
+out:
+	return error;
+}
+
+static int __iext_convert_to_inline_extent(struct inode *inode,
+				       struct folio *direct_folio,
+				       struct folio *extent_folio)
+{
+	int i = 0, error, direct_blocks = ADDRS_PER_INODE(inode);
+	__le32 *arr = (__le32 *)get_dnode_addr(inode, direct_folio);
+
+	for (; i < direct_blocks; i++) {
+		error = __iext_insert_idx(inode, extent_folio, i,
+					le32_to_cpu(arr[i]));
+		if (error)
+			return error;
+	}
+
+	return 0;
+}
+
+bool f2fs_iext_convert_to_inline_extent(struct inode *inode,
+					struct folio *ifolio)
+{
+	struct f2fs_iext_info *iext_info = F2FS_I_SB(inode)->iext_info;
+	bool updated = false;
+	int ret;
+
+	if (f2fs_iext_support_inline_extent(inode, ifolio))
+		goto out;
+
+	mutex_lock(&iext_info->convert_lock);
+	memcpy(folio_address(iext_info->convert_folio),
+				folio_address(ifolio), PAGE_SIZE);
+	f2fs_iext_init_inline_extent(inode, ifolio);
+	ret = __iext_convert_to_inline_extent(inode, iext_info->convert_folio,
+				ifolio);
+	if (ret)
+		memcpy(folio_address(ifolio),
+				folio_address(iext_info->convert_folio),
+				PAGE_SIZE);
+	else
+		updated = true;
+	mutex_unlock(&iext_info->convert_lock);
+
+out:
+	return updated;
+}
+
+static void __iext_convert_to_direct_blocks(struct inode *inode,
+					struct folio *direct_folio,
+					struct folio *extent_folio)
+{
+	int i = 0, direct_blocks = ADDRS_PER_INODE(inode);
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)get_dnode_addr(inode, extent_folio);
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+	int j, len;
+	struct f2fs_extent *ext;
+	block_t fofs_start = 0, blk_start;
+	__le32 *arr = (__le32 *)get_dnode_addr(inode, direct_folio);
+
+	/* set default to NULL_ADDR. */
+	memset(arr, 0, sizeof(__le32) * direct_blocks);
+	for (; fofs_start < direct_blocks && i < inline_extents; i++) {
+		ext = &eh->exts[i];
+		len = F2FS_EXT_LEN(ext);
+		fofs_start = F2FS_EXT_LOGICAL_START(ext);
+		blk_start = F2FS_EXT_PHYSICAL_START(ext);
+		for (j = 0; j < len && fofs_start < direct_blocks; j++) {
+			if (blk_start == NEW_ADDR)
+				arr[fofs_start++] = cpu_to_le32(blk_start);
+			else
+				arr[fofs_start++] = cpu_to_le32(blk_start + j);
+		}
+	}
+}
+
+void f2fs_iext_convert_to_direct_blocks(struct inode *inode,
+					struct folio *ifolio)
+{
+	struct f2fs_iext_info *iext_info = F2FS_I_SB(inode)->iext_info;
+
+	mutex_lock(&iext_info->convert_lock);
+	memcpy_to_folio(iext_info->convert_folio, 0,
+			folio_address(ifolio), PAGE_SIZE);
+	__iext_convert_to_direct_blocks(inode, ifolio,
+			iext_info->convert_folio);
+	mutex_unlock(&iext_info->convert_lock);
+}
+
+/* truncate extent starting from @fofs. */
+void f2fs_iext_truncate_from_blkaddr(struct inode *inode,
+					struct folio *ifolio, block_t fofs)
+{
+	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+	int index, hit;
+	unsigned int len;
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)get_dnode_addr(inode, ifolio);
+	int inline_extents = EXT_ENTRY_COUNT(eh);
+	struct f2fs_extent *ext;
+	block_t fofs_start;
+
+	if (inline_extents == 0)
+		return;
+	index = __iext_binsearch_idx(inode, ifolio, fofs);
+
+	ext = &eh->exts[index];
+
+	hit = __is_iextent_hit(ext, fofs);
+	switch (hit) {
+	case F2FS_EXT_HIT_LEFT:
+		ASSERT(sbi, index == 0);
+		eh->cnt = cpu_to_le32(0);
+		break;
+	case F2FS_EXT_HIT_RIGHT:
+		eh->cnt = cpu_to_le32(index + 1);
+		break;
+	case F2FS_EXT_HIT_MID:
+		fofs_start = F2FS_EXT_LOGICAL_START(ext);
+		len = F2FS_EXT_LEN(ext);
+
+		if (len == 1 || fofs == fofs_start) {
+			eh->cnt = cpu_to_le32(index);
+			break;
+		}
+		ext->len = cpu_to_le32(fofs - fofs_start);
+		eh->cnt = cpu_to_le32(index + 1);
+		break;
+	default:
+		ASSERT(sbi, 0);
+	}
+}
+
+int f2fs_iext_info_init(struct f2fs_sb_info *sbi)
+{
+	struct f2fs_iext_info *iext_info;
+	struct page *page;
+
+	iext_info = f2fs_kvzalloc(sbi, sizeof(struct f2fs_iext_info),
+				GFP_KERNEL);
+	if (!iext_info)
+		return -ENOMEM;
+
+	page = alloc_pages(GFP_KERNEL, 0);
+	if (!page) {
+		kvfree(iext_info);
+		return -ENOMEM;
+	}
+
+	iext_info->convert_folio = page_folio(page);
+	mutex_init(&iext_info->convert_lock);
+	spin_lock_init(&iext_info->iext_ext_lock);
+	sbi->iext_info = iext_info;
+	return 0;
+}
+
+void f2fs_iext_info_destroy(struct f2fs_sb_info *sbi)
+{
+	if (sbi->iext_info == NULL)
+		return;
+	__free_page(folio_page(sbi->iext_info->convert_folio, 0));
+	kvfree(sbi->iext_info);
+}
+
+int f2fs_iext_update_extension_list(struct f2fs_sb_info *sbi, const char *name,
+					bool set)
+{
+	struct f2fs_iext_info *iext_info = sbi->iext_info;
+	__u8 (*extlist)[F2FS_EXTENSION_LEN] = iext_info->extensions;
+	int count;
+	int i, ret = 0;
+	unsigned long flag;
+
+	if (strlen(name) >= F2FS_EXTENSION_LEN)
+		return -EINVAL;
+
+	spin_lock_irqsave(&iext_info->iext_ext_lock, flag);
+	count = iext_info->iext_ext_cnt;
+	if (set && count == IEXT_EXT_NUM) {
+		ret = -EINVAL;
+		goto out;
+	}
+	for (i = 0; i < count; i++) {
+		if (strcmp(name, extlist[i]))
+			continue;
+
+		if (set) {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		memcpy(extlist[i], extlist[i + 1],
+				F2FS_EXTENSION_LEN * (count - i - 1));
+		memset(extlist[count - 1], 0, F2FS_EXTENSION_LEN);
+		iext_info->iext_ext_cnt--;
+		goto out;
+	}
+
+	if (!set) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	memcpy(extlist[count], name, strlen(name));
+	iext_info->iext_ext_cnt++;
+out:
+	spin_unlock_irqrestore(&iext_info->iext_ext_lock, flag);
+	return ret;
+}
diff --git a/fs/f2fs/iextent.h b/fs/f2fs/iextent.h
new file mode 100644
index 000000000000..e5fb028612ad
--- /dev/null
+++ b/fs/f2fs/iextent.h
@@ -0,0 +1,184 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * fs/f2fs/iextent.h
+ *
+ * Copyright (c) 2026 Xiaomi Technology Co., Ltd.
+ *             http://www.mi.com/
+ */
+#ifndef __F2FS_INLINE_EXTENT_H_
+#define __F2FS_INLINE_EXTENT_H_
+
+#include <linux/types.h>
+
+#define F2FS_IEXT_FORMAT_MAGIC1	0xF2F5EF00 /* F2FS Extent Format. */
+/* Use invalid data blkaddr as inline-extent identifier. */
+#define F2FS_IEXT_FORMAT_MAGIC2	(-3U)
+struct f2fs_iext_header {
+	__le32 magic1;		/* magic1 # to identify extent format. */
+	/* magic2 # cannot be a valid blkaddr in non-inline-extent format.*/
+	__le32 magic2;
+	__le32 cnt;		/* total # of extents. */
+	struct f2fs_extent exts[];
+};
+
+#define MAX_INLINE_EXTENTS(inode)	((ADDRS_PER_INODE(inode) * \
+			sizeof(__le32) - sizeof(struct f2fs_iext_header)) / \
+			sizeof(struct f2fs_extent))
+
+#define EXT_FIRST_INDEX(__hdr__) ((__hdr__)->exts)
+#define EXT_ENTRY_COUNT(__hdr__) (le32_to_cpu((__hdr__)->cnt))
+#define EXT_LAST_INDEX(__hdr__) \
+			(EXT_ENTRY_COUNT(__hdr__) ? \
+			&(__hdr__)->exts[EXT_ENTRY_COUNT(__hdr__) - 1] : \
+			NULL)
+
+#define F2FS_EXT_LEN(ext) (le32_to_cpu((ext)->len))
+#define F2FS_EXT_LOGICAL_START(ext) (le32_to_cpu((ext)->fofs))
+#define F2FS_EXT_LOGICAL_END(ext) \
+			((F2FS_EXT_LOGICAL_START(ext) + F2FS_EXT_LEN(ext)) - 1)
+#define F2FS_EXT_PHYSICAL_START(ext) (le32_to_cpu((ext)->blk))
+#define F2FS_EXT_PHYSICAL_END(ext) \
+			((F2FS_EXT_PHYSICAL_START(ext) + F2FS_EXT_LEN(ext)) - 1)
+
+#define IEXT_EXT_NUM 16
+struct f2fs_iext_info {
+	/* preallocated folio used for format conversion. */
+	struct folio *convert_folio;
+	struct mutex convert_lock;	/* protects convert_folio access */
+
+	spinlock_t iext_ext_lock;
+	unsigned char iext_ext_cnt;	/* extension count */
+	unsigned char extensions[IEXT_EXT_NUM][F2FS_EXTENSION_LEN];
+	bool iext_enable;
+#ifdef CONFIG_F2FS_INLINE_EXTENT_DEBUG
+	/* stat for inline extent code coverage. */
+	atomic64_t left_merge_count;
+	atomic64_t right_merge_count;
+	atomic64_t fast_right_merge_count;
+	atomic64_t lr_merge_count;
+	atomic64_t split_left_count;
+	atomic64_t split_right_count;
+	atomic64_t split_mid_count;
+	atomic64_t insert_new_ext_count;
+	atomic64_t overwrite_ext_count;
+	atomic64_t add_ext_count;
+	atomic64_t del_ext_count;
+	atomic64_t evict_last_ext_cnt;
+	atomic64_t truncate_last_ext_cnt;
+	atomic64_t drop_insert_new_ext_cnt;
+	atomic64_t trigger_ext_format_convert;
+#endif
+};
+
+enum {
+	/* insert extent correctly */
+	F2FS_IEXT_INSERT_NORMAL = 0,
+	/* inline area cannot cover all direct blocks */
+	F2FS_IEXT_INSERT_CONVERT,
+	/* didn't modify inode folio, because no space and fofs is too big */
+	F2FS_IEXT_INSERT_DROP,
+	/* modify inode folio, but didn't insert due to no space */
+	F2FS_IEXT_INSERT_REMOVED,
+};
+
+#ifdef CONFIG_F2FS_INLINE_EXTENT_DEBUG
+#define INLINE_EXT_STAT_INC(iext_info, member) \
+			atomic64_inc(&(iext_info)->member)
+#else
+#define INLINE_EXT_STAT_INC(iext_info, member) do { } while (0)
+#endif
+
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+static inline bool f2fs_iext_support_inline_extent(struct inode *inode,
+			struct folio *ifolio)
+{
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)get_dnode_addr(inode, ifolio);
+
+	if (__le32_to_cpu(eh->magic1) == F2FS_IEXT_FORMAT_MAGIC1 &&
+			__le32_to_cpu(eh->magic2) == F2FS_IEXT_FORMAT_MAGIC2)
+		return true;
+	return false;
+}
+
+static inline void f2fs_iext_init_inline_extent(struct inode *inode,
+			struct folio *ifolio)
+{
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)get_dnode_addr(inode, ifolio);
+
+	eh->magic1 = __cpu_to_le32(F2FS_IEXT_FORMAT_MAGIC1);
+	eh->magic2 = __cpu_to_le32(F2FS_IEXT_FORMAT_MAGIC2);
+	eh->cnt = 0;
+}
+
+static inline bool f2fs_iext_is_enable(struct f2fs_sb_info *sbi)
+{
+	struct f2fs_iext_info *iext_info = sbi->iext_info;
+
+	return READ_ONCE(iext_info->iext_enable);
+}
+
+static inline void f2fs_iext_set_enable(struct f2fs_sb_info *sbi, bool enable)
+{
+	struct f2fs_iext_info *iext_info = sbi->iext_info;
+
+	WRITE_ONCE(iext_info->iext_enable, enable);
+}
+
+static inline block_t f2fs_iext_last_fofs(struct inode *inode,
+			struct folio *ifolio)
+{
+	struct f2fs_iext_header *eh =
+		(struct f2fs_iext_header *)get_dnode_addr(inode, ifolio);
+
+	if (EXT_ENTRY_COUNT(eh) == 0)
+		return 0;
+
+	return F2FS_EXT_LOGICAL_END(EXT_LAST_INDEX(eh));
+}
+#else
+static inline bool f2fs_iext_support_inline_extent(struct inode *inode,
+			struct folio *ifolio)
+{
+	return false;
+}
+
+static inline void f2fs_iext_init_inline_extent(struct inode *inode,
+			struct folio *ifolio)
+{
+}
+
+static inline bool f2fs_iext_is_enable(struct f2fs_sb_info *sbi)
+{
+	return false;
+}
+
+static inline void f2fs_iext_set_enable(struct f2fs_sb_info *sbi, bool enable)
+{
+}
+
+static inline block_t f2fs_iext_last_fofs(struct inode *inode,
+			struct folio *ifolio)
+{
+	return 0;
+}
+#endif
+
+bool f2fs_iext_sanity_check(struct folio *ifolio);
+int f2fs_iext_lookup_blkaddr(struct inode *inode, struct folio *ifolio,
+					block_t fofs, block_t *blkaddr,
+					unsigned int *len);
+int f2fs_iext_update_data_blkaddr(struct inode *inode, struct folio *ifolio,
+					block_t fofs, block_t blkaddr);
+bool f2fs_iext_convert_to_inline_extent(struct inode *inode,
+					struct folio *ifolio);
+void f2fs_iext_convert_to_direct_blocks(struct inode *inode,
+					struct folio *ifolio);
+void f2fs_iext_truncate_from_blkaddr(struct inode *inode,
+					struct folio *ifolio, block_t fofs);
+int f2fs_iext_update_extension_list(struct f2fs_sb_info *sbi, const char *name,
+					bool set);
+int f2fs_iext_info_init(struct f2fs_sb_info *sbi);
+void f2fs_iext_info_destroy(struct f2fs_sb_info *sbi);
+#endif
diff --git a/fs/f2fs/inline.c b/fs/f2fs/inline.c
index 099f72089701..8b4f9359a471 100644
--- a/fs/f2fs/inline.c
+++ b/fs/f2fs/inline.c
@@ -13,6 +13,7 @@
 
 #include "f2fs.h"
 #include "node.h"
+#include "iextent.h"
 #include <trace/events/f2fs.h>
 
 static bool support_inline_data(struct inode *inode)
@@ -212,6 +213,12 @@ int f2fs_convert_inline_folio(struct dnode_of_data *dn, struct folio *folio)
 clear_out:
 	stat_dec_inline_inode(dn->inode);
 	clear_inode_flag(dn->inode, FI_INLINE_DATA);
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+	if (F2FS_I(dn->inode)->i_flags & F2FS_EXTENT_FL)
+		if (f2fs_iext_convert_to_inline_extent(
+					dn->inode, dn->inode_folio))
+			folio_mark_dirty(dn->inode_folio);
+#endif
 	f2fs_put_dnode(dn);
 	return 0;
 }
diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c
index cac03b8e91a1..f6afb3f66611 100644
--- a/fs/f2fs/namei.c
+++ b/fs/f2fs/namei.c
@@ -20,6 +20,7 @@
 #include "segment.h"
 #include "xattr.h"
 #include "acl.h"
+#include "iextent.h"
 #include <trace/events/f2fs.h>
 
 static inline bool is_extension_exist(const unsigned char *s, const char *sub,
@@ -70,6 +71,13 @@ static inline bool is_compress_extension(const unsigned char *s, const char *sub
 	return is_extension_exist(s, sub, true, true);
 }
 
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+static inline bool is_iext_extension(const unsigned char *s, const char *sub)
+{
+	return is_extension_exist(s, sub, true, true);
+}
+#endif
+
 int f2fs_update_extension_list(struct f2fs_sb_info *sbi, const char *name,
 							bool hot, bool set)
 {
@@ -231,6 +239,42 @@ static void set_file_temperature(struct f2fs_sb_info *sbi, struct inode *inode,
 		file_set_hot(inode);
 }
 
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+static void set_iext_new_inode(struct f2fs_sb_info *sbi, struct inode *inode,
+				const unsigned char *name)
+{
+	struct f2fs_iext_info *iext_info = sbi->iext_info;
+	unsigned char (*ext)[F2FS_EXTENSION_LEN] = iext_info->extensions;
+	unsigned long flag;
+	int i;
+
+	if (f2fs_compressed_file(inode))
+		return;
+
+	if (S_ISDIR(inode->i_mode))
+		return;
+
+	if (!f2fs_iext_is_enable(sbi))
+		return;
+
+	/* This name comes only from normal files. */
+	if (!name)
+		return;
+
+	spin_lock_irqsave(&iext_info->iext_ext_lock, flag);
+	/* Mark wanting extension file as inline extent format. */
+	for (i = 0; i < iext_info->iext_ext_cnt; i++) {
+		if (is_iext_extension(name, ext[i])) {
+			F2FS_I(inode)->i_flags |= F2FS_EXTENT_FL;
+			spin_unlock_irqrestore(&iext_info->iext_ext_lock, flag);
+			f2fs_mark_inode_dirty_sync(inode, true);
+			return;
+		}
+	}
+	spin_unlock_irqrestore(&iext_info->iext_ext_lock, flag);
+}
+#endif
+
 static struct inode *f2fs_new_inode(struct mnt_idmap *idmap,
 						struct inode *dir, umode_t mode,
 						const char *name)
@@ -327,6 +371,10 @@ static struct inode *f2fs_new_inode(struct mnt_idmap *idmap,
 	/* Check compression first. */
 	set_compress_new_inode(sbi, dir, inode, name);
 
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+	/* must be set after compress flag set. */
+	set_iext_new_inode(sbi, inode, name);
+#endif
 	/* Should enable inline_data after compression set */
 	if (test_opt(sbi, INLINE_DATA) && f2fs_may_inline_data(inode))
 		set_inode_flag(inode, FI_INLINE_DATA);
diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c
index cd5a394f6111..2317de756322 100644
--- a/fs/f2fs/node.c
+++ b/fs/f2fs/node.c
@@ -19,6 +19,7 @@
 #include "segment.h"
 #include "xattr.h"
 #include "iostat.h"
+#include "iextent.h"
 #include <trace/events/f2fs.h>
 
 #define on_f2fs_build_free_nids(nm_i) mutex_is_locked(&(nm_i)->build_lock)
@@ -819,6 +820,7 @@ int f2fs_get_dnode_of_data(struct dnode_of_data *dn, pgoff_t index, int mode)
 	nid_t nids[4];
 	int level, i = 0;
 	int err = 0;
+	bool lock_ifolio = false;
 
 	level = get_node_path(dn->inode, index, offset, noffset);
 	if (level < 0)
@@ -846,6 +848,8 @@ int f2fs_get_dnode_of_data(struct dnode_of_data *dn, pgoff_t index, int mode)
 		nids[1] = get_nid(parent, offset[0], true);
 	dn->inode_folio = nfolio[0];
 	dn->inode_folio_locked = true;
+	if (f2fs_iext_support_inline_extent(dn->inode, dn->inode_folio))
+		lock_ifolio = true;
 
 	/* get indirect or direct nodes */
 	for (i = 1; i <= level; i++) {
@@ -888,8 +892,10 @@ int f2fs_get_dnode_of_data(struct dnode_of_data *dn, pgoff_t index, int mode)
 			done = true;
 		}
 		if (i == 1) {
-			dn->inode_folio_locked = false;
-			folio_unlock(parent);
+			if (!lock_ifolio) {
+				dn->inode_folio_locked = false;
+				folio_unlock(parent);
+			}
 		} else {
 			f2fs_folio_put(parent, true);
 		}
@@ -899,7 +905,8 @@ int f2fs_get_dnode_of_data(struct dnode_of_data *dn, pgoff_t index, int mode)
 						NODE_TYPE_NON_INODE);
 			if (IS_ERR(nfolio[i])) {
 				err = PTR_ERR(nfolio[i]);
-				f2fs_folio_put(nfolio[0], false);
+				f2fs_folio_put(nfolio[0],
+						dn->inode_folio_locked);
 				goto release_out;
 			}
 		}
@@ -945,7 +952,7 @@ int f2fs_get_dnode_of_data(struct dnode_of_data *dn, pgoff_t index, int mode)
 release_pages:
 	f2fs_folio_put(parent, true);
 	if (i > 1)
-		f2fs_folio_put(nfolio[0], false);
+		f2fs_folio_put(nfolio[0], dn->inode_folio_locked);
 release_out:
 	dn->inode_folio = NULL;
 	dn->node_folio = NULL;
@@ -1194,6 +1201,7 @@ int f2fs_truncate_inode_blocks(struct inode *inode, pgoff_t from)
 	unsigned int nofs = 0;
 	struct dnode_of_data dn;
 	struct folio *folio;
+	bool lock_ifolio = false;
 
 	trace_f2fs_truncate_inode_blocks_enter(inode, from);
 
@@ -1217,7 +1225,16 @@ int f2fs_truncate_inode_blocks(struct inode *inode, pgoff_t from)
 	}
 
 	set_new_dnode(&dn, inode, folio, NULL, 0);
-	folio_unlock(folio);
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+	if (f2fs_iext_support_inline_extent(inode, folio)) {
+		lock_ifolio = true;
+		f2fs_iext_truncate_from_blkaddr(inode, folio,
+				MAX(ADDRS_PER_INODE(inode), from));
+	} else
+#endif
+	{
+		folio_unlock(folio);
+	}
 
 	switch (level) {
 	case 0:
@@ -1282,17 +1299,19 @@ int f2fs_truncate_inode_blocks(struct inode *inode, pgoff_t from)
 		if (err < 0)
 			goto fail;
 		if (offset[1] == 0 && get_nid(folio, offset[0], true)) {
-			folio_lock(folio);
+			if (!lock_ifolio)
+				folio_lock(folio);
 			BUG_ON(!is_node_folio(folio));
 			set_nid(folio, offset[0], 0, true);
-			folio_unlock(folio);
+			if (!lock_ifolio)
+				folio_unlock(folio);
 		}
 		offset[1] = 0;
 		offset[0]++;
 		nofs += err;
 	}
 fail:
-	f2fs_folio_put(folio, false);
+	f2fs_folio_put(folio, lock_ifolio);
 	trace_f2fs_truncate_inode_blocks_exit(inode, err);
 	return err > 0 ? 0 : err;
 }
@@ -1608,6 +1627,14 @@ static struct folio *__get_node_folio(struct f2fs_sb_info *sbi, pgoff_t nid,
 		err = -EFSBADCRC;
 		goto out_err;
 	}
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+	if (IS_INODE(folio) &&
+			f2fs_iext_support_inline_extent(NULL, folio) &&
+			!f2fs_iext_sanity_check(folio)) {
+		err = -EINVAL;
+		goto out_err;
+	}
+#endif
 page_hit:
 	err = f2fs_sanity_check_node_footer(sbi, folio, nid, ntype, false);
 	if (!err)
@@ -3577,3 +3604,26 @@ void f2fs_destroy_node_manager_caches(void)
 	kmem_cache_destroy(free_nid_slab);
 	kmem_cache_destroy(nat_entry_slab);
 }
+
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+int f2fs_iext_data_blkaddr(struct inode *inode,
+			struct folio *node_folio, unsigned int offset,
+			block_t *blkaddr)
+{
+	int ret = -EINVAL;
+
+	if (IS_INODE(node_folio) &&
+			f2fs_iext_support_inline_extent(inode, node_folio)) {
+		block_t fofs = f2fs_start_bidx_of_node(
+						ofs_of_node(node_folio),
+						inode) + offset;
+		f2fs_bug_on(F2FS_I_SB(inode), fofs != offset);
+		f2fs_bug_on(F2FS_I_SB(inode), !folio_test_locked(node_folio));
+
+		ret = f2fs_iext_lookup_blkaddr(inode, node_folio, fofs, blkaddr,
+						NULL);
+		f2fs_bug_on(F2FS_I_SB(inode), ret);
+	}
+	return ret;
+}
+#endif
diff --git a/fs/f2fs/node.h b/fs/f2fs/node.h
index 5e114f352099..28c10787e5bb 100644
--- a/fs/f2fs/node.h
+++ b/fs/f2fs/node.h
@@ -259,6 +259,10 @@ static inline unsigned int ofs_of_node(const struct folio *node_folio)
 	return flag >> OFFSET_BIT_SHIFT;
 }
 
+int f2fs_iext_data_blkaddr(struct inode *inode,
+			struct folio *node_folio, unsigned int offset,
+			block_t *blkaddr);
+
 static inline __u64 cpver_of_node(const struct folio *node_folio)
 {
 	struct f2fs_node *rn = F2FS_NODE(node_folio);
diff --git a/fs/f2fs/recovery.c b/fs/f2fs/recovery.c
index 89af8407b667..94f0d7c2e42e 100644
--- a/fs/f2fs/recovery.c
+++ b/fs/f2fs/recovery.c
@@ -13,6 +13,7 @@
 #include "f2fs.h"
 #include "node.h"
 #include "segment.h"
+#include "iextent.h"
 
 /*
  * Roll forward recovery scenarios.
@@ -665,6 +666,22 @@ static int do_recover_data(struct f2fs_sb_info *sbi, struct inode *inode,
 		}
 		goto out;
 	}
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+	if (f2fs_iext_support_inline_extent(inode,
+			dn.inode_folio)) {
+		f2fs_bug_on(F2FS_I_SB(inode),
+				!dn.inode_folio_locked);
+		f2fs_bug_on(F2FS_I_SB(inode),
+				!folio_test_locked(dn.inode_folio));
+		f2fs_folio_wait_writeback(dn.inode_folio, NODE, true, true);
+		if (f2fs_iext_last_fofs(inode, dn.inode_folio) >=
+				ADDRS_PER_INODE(inode)) {
+			f2fs_iext_truncate_from_blkaddr(inode,
+					dn.inode_folio, ADDRS_PER_INODE(inode));
+			folio_mark_dirty(dn.inode_folio);
+		}
+	}
+#endif
 
 	f2fs_folio_wait_writeback(dn.node_folio, NODE, true, true);
 
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index 20577e33ee2a..3025ef1ca09a 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -37,6 +37,7 @@
 #include "xattr.h"
 #include "gc.h"
 #include "iostat.h"
+#include "iextent.h"
 
 #define CREATE_TRACE_POINTS
 #include <trace/events/f2fs.h>
@@ -5037,6 +5038,12 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc)
 
 	sbi->sb = sb;
 
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+	err = f2fs_iext_info_init(sbi);
+	if (err)
+		goto free_sbi;
+#endif
+
 	/* initialize locks within allocated memory */
 	init_f2fs_rwsem_trace(&sbi->gc_lock, sbi, LOCK_NAME_GC_LOCK);
 	mutex_init(&sbi->writepages);
@@ -5524,6 +5531,9 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc)
 free_sbi:
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 	lockdep_unregister_key(&sbi->cp_global_sem_key);
+#endif
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+	f2fs_iext_info_destroy(sbi);
 #endif
 	kfree(sbi);
 	sb->s_fs_info = NULL;
@@ -5608,6 +5618,9 @@ static void kill_f2fs_super(struct super_block *sb)
 		destroy_device_list(sbi);
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 		lockdep_unregister_key(&sbi->cp_global_sem_key);
+#endif
+#ifdef CONFIG_F2FS_INLINE_EXTENT
+		f2fs_iext_info_destroy(sbi);
 #endif
 		kfree(sbi);
 		sb->s_fs_info = NULL;
-- 
2.43.0



_______________________________________________
Linux-f2fs-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.