Re: [PATCH v6 3/3] iomap: add simple dio path for small direct I/O

From: changfengnan

Date: Wed Jul 08 2026 - 04:52:35 EST



> From: "Joanne Koong"<joannelkoong@xxxxxxxxx>
> Date:  Wed, Jul 8, 2026, 10:07
> Subject:  Re: [PATCH v6 3/3] iomap: add simple dio path for small direct I/O
> To: "changfengnan"<changfengnan@xxxxxxxxxxxxx>
> Cc: "Christoph Hellwig"<hch@xxxxxxxxxxxxx>, "brauner"<brauner@xxxxxxxxxx>, "djwong"<djwong@xxxxxxxxxx>, "ojaswin"<ojaswin@xxxxxxxxxxxxx>, "dgc"<dgc@xxxxxxxxxx>, "linux-xfs"<linux-xfs@xxxxxxxxxxxxxxx>, "linux-fsdevel"<linux-fsdevel@xxxxxxxxxxxxxxx>, "linux-ext4"<linux-ext4@xxxxxxxxxxxxxxx>, "linux-kernel"<linux-kernel@xxxxxxxxxxxxxxx>, <lidiangang@xxxxxxxxxxxxx>, "pankaj.raghav"<pankaj.raghav@xxxxxxxxx>, "Brian Foster"<bfoster@xxxxxxxxxx>
> On Mon, Jul 6, 2026 at 11:57 PM changfengnan <changfengnan@xxxxxxxxxxxxx> wrote:
> >
> >
> > > From: "Joanne Koong"<joannelkoong@xxxxxxxxx>
> > > Date:  Tue, Jul 7, 2026, 03:27
> > > Subject:  Re: [PATCH v6 3/3] iomap: add simple dio path for small direct I/O
> > > To: "changfengnan"<changfengnan@xxxxxxxxxxxxx>
> > > Cc: "Christoph Hellwig"<hch@xxxxxxxxxxxxx>, "brauner"<brauner@xxxxxxxxxx>, "djwong"<djwong@xxxxxxxxxx>, "ojaswin"<ojaswin@xxxxxxxxxxxxx>, "dgc"<dgc@xxxxxxxxxx>, "linux-xfs"<linux-xfs@xxxxxxxxxxxxxxx>, "linux-fsdevel"<linux-fsdevel@xxxxxxxxxxxxxxx>, "linux-ext4"<linux-ext4@xxxxxxxxxxxxxxx>, "linux-kernel"<linux-kernel@xxxxxxxxxxxxxxx>, <lidiangang@xxxxxxxxxxxxx>, "pankaj.raghav"<pankaj.raghav@xxxxxxxxx>, "Brian Foster"<bfoster@xxxxxxxxxx>
> > > On Mon, Jul 6, 2026 at 4:10 AM changfengnan <changfengnan@xxxxxxxxxxxxx> wrote:
> > > >
> > > > > From: "Christoph Hellwig"<hch@xxxxxxxxxxxxx>
> > > > > Date:  Fri, Jul 3, 2026, 20:40
> > > > > Subject:  Re: [PATCH v6 3/3] iomap: add simple dio path for small direct I/O
> > > > > To: "changfengnan"<changfengnan@xxxxxxxxxxxxx>
> > > > > Cc: "Joanne Koong"<joannelkoong@xxxxxxxxx>, "Christoph Hellwig"<hch@xxxxxxxxxxxxx>, "brauner"<brauner@xxxxxxxxxx>, "djwong"<djwong@xxxxxxxxxx>, "ojaswin"<ojaswin@xxxxxxxxxxxxx>, "dgc"<dgc@xxxxxxxxxx>, "linux-xfs"<linux-xfs@xxxxxxxxxxxxxxx>, "linux-fsdevel"<linux-fsdevel@xxxxxxxxxxxxxxx>, "linux-ext4"<linux-ext4@xxxxxxxxxxxxxxx>, "linux-kernel"<linux-kernel@xxxxxxxxxxxxxxx>, <lidiangang@xxxxxxxxxxxxx>, "pankaj.raghav"<pankaj.raghav@xxxxxxxxx>, "Brian Foster"<bfoster@xxxxxxxxxx>
> > > > > On Fri, Jul 03, 2026 at 05:13:12PM +0800, changfengnan wrote:
> > > > > > taken, which introduced some overhead.
> > > > > > I implemented some optimizations based on Christoph’s suggestions and
> > > > > > made some modifications to `iomap_process`;
> > > > > > Now, XFS performance is essentially unchanged, while ext4 still shows a
> > > > > > 1.5% drop; I will continue to investigate the cause..   I’ve attached the patch.
> > > > > > Actually, I had expected that switching to `iomap_next` would improve
> > > > > > performance, since it avoids an indirect call.
> > > > >
> > > > > I don't think it will for you - the direct I/O readers don't have
> > > > > iomap_end methods, so you still have the same number of indirect
> > > > > calls.  What could help is to inline the iterator as seen in Joannes'
> > > > > fist demo series.  We could even look into doing that only for simple
> > > > > dio in a first step, shifting the burden to use the simple method
> > > > > to the callers (at least for the POC).
> > > >
> > > > I'm not sure if this is what you had in mind, but I made some
> > > > adjustments referencing Joannes' first patch, and using this approach,
> > > > there was no performance degradation.
> > >
> > > Thanks, Fengnan. This is awesome that it got the degradation down to zero.
> > >
> > > Would you be able to test how this change performs on your benchmarks?:
> >
> > XFS basically has no performance degradation, while ext4 experiences
> > a 1-1.5% performance degradation.
> >
> > Round 1:
> >     d67      xfs : 2.04M, 2.05M, 2.04M, 2.04M, 2.04M  avg=2.042M
> >     d67      ext4: 2.12M, 2.12M, 2.12M, 2.12M, 2.12M  avg=2.120M
> >     joanne86 xfs : 2.13M, 2.05M, 2.04M, 2.05M, 2.05M  avg=2.064M
> >     joanne86 ext4: 2.11M, 2.11M, 2.11M, 2.11M, 2.11M  avg=2.110M
> >
> >   Round 2:
> >     d67      xfs : 2.05M, 2.05M, 2.05M, 2.05M, 2.05M  avg=2.050M
> >     d67      ext4: 2.12M, 2.12M, 2.12M, 2.12M, 2.12M  avg=2.120M
> >     joanne86 xfs : 2.04M, 2.04M, 2.04M, 2.04M, 2.05M  avg=2.042M
> >     joanne86 ext4: 2.09M, 2.08M, 2.09M, 2.10M, 2.09M  avg=2.090M
> >
> >   Round 3:
> >     d67      xfs : 2.04M, 2.04M, 2.04M, 2.04M, 2.05M  avg=2.042M
> >     d67      ext4: 2.12M, 2.12M, 2.11M, 2.12M, 2.12M  avg=2.118M
> >     joanne86 xfs : 2.04M, 2.05M, 2.04M, 2.04M, 2.04M  avg=2.042M
> >     joanne86 ext4: 2.09M, 2.09M, 2.08M, 2.09M, 2.09M  avg=2.088M
> >
> 
> Thanks. I'm not sure why it makes ext4 worse. Maybe it being inlined
> bloats it too much so it leads to more frontend stalls/icache misses
> than what the devirtualization helps with? Looking at the begin
> functions, ext4's is around 2.5x larger than xfs's. It also looks like
> ext4's read_iomap_begin function has 2 callers, so I think that may
> not get automatically inlined whereas xfs's will since
> xfs_read_iomap_begin just has 1 caller, though I don't know if that
> ends up mattering.

I ran two rounds of tests using your patch, and ext4 still showed a 1.5%
performance drop.
However, I think your analysis is correct—it’s likely caused by too many
inline functions.  Inlining the simple DIO wrappers expands the filesystem
read_iter hot path and increases frontend/register pressure. Keeping the
wrappers out of line adds only a cheap direct call, while preserving a smaller
and better laid-out filesystem hot path.
So I made some changes, reverting `iomap_dio_rw()` and `iomap_dio_simple()`
to non-inline versions, and performance returned to normal.

Patch based on https://github.com/joannekoong/linux/commits/iomap_simple_dio_iomap_next_ext4/ ;
diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c
index bc3b87e57592a..d0f7ef8ac140a 100644
--- a/fs/iomap/direct-io.c
+++ b/fs/iomap/direct-io.c
@@ -968,6 +968,33 @@ static void iomap_dio_simple_end_io(struct bio *bio)
         iocb->ki_complete(iocb, iomap_dio_simple_complete(sr));
 }
 
+static bool iomap_dio_simple_supported(struct kiocb *iocb,
+                                       struct iov_iter *iter,
+                                       const struct iomap_dio_ops *dops,
+                                       unsigned int dio_flags,
+                                       size_t done_before)
+{
+        struct inode *inode = file_inode(iocb->ki_filp);
+        size_t count = iov_iter_count(iter);
+
+        if (dops || done_before)
+                return false;
+        if (iov_iter_rw(iter) != READ)
+                return false;
+        if (!count)
+                return false;
+        if (count > inode->i_sb->s_blocksize)
+                return false;
+        if (dio_flags & (IOMAP_DIO_FORCE_WAIT | IOMAP_DIO_PARTIAL |
+                         IOMAP_DIO_BOUNCE))
+                return false;
+        if (iocb->ki_pos + count > i_size_read(inode))
+                return false;
+        if (IS_ENCRYPTED(inode))
+                return false;
+        return true;
+}
+
 static inline void
 iomap_dio_simple_finish(struct iomap_iter *iomi, iomap_next_fn iomap_next,
                 size_t written)
@@ -978,7 +1005,7 @@ iomap_dio_simple_finish(struct iomap_iter *iomi, iomap_next_fn iomap_next,
         iomap_next(iomi, &iomi->iomap, &iomi->srcmap);
 }
 
-ssize_t
+static ssize_t
 __iomap_dio_simple(struct kiocb *iocb, struct iov_iter *iter,
                 struct iomap_iter *iomi, iomap_next_fn iomap_next,
                 unsigned int dio_flags)
@@ -1070,7 +1097,97 @@ __iomap_dio_simple(struct kiocb *iocb, struct iov_iter *iter,
         inode_dio_end(inode);
         return ret;
 }
-EXPORT_SYMBOL_GPL(__iomap_dio_simple);
+
+/*
+ * Fast path for small, block-aligned direct I/Os that map to a single
+ * contiguous on-disk extent.
+ *
+ * iomap_dio_simple_supported() enforces the cheap up-front constraints before
+ * entering this path.
+ *
+ * @dops must be NULL: a non-NULL @dops means the caller wants its
+ * ->end_io / ->submit_io hooks invoked, and in particular wants its bios to be
+ * allocated from the filesystem-private @dops->bio_set (whose front_pad sizes a
+ * filesystem-private wrapper around the bio).  The fast path instead allocates
+ * from the shared iomap_dio_simple_pool, whose front_pad matches struct
+ * iomap_dio_simple; the two wrappers are not interchangeable, so we must fall
+ * back to __iomap_dio_rw() in that case.
+ *
+ * @done_before must be zero: a non-zero caller-accumulated residual cannot be
+ * carried through a single-bio inline completion.
+ *
+ * @iter must describe a non-empty READ no larger than the inode block size:
+ * writes, zero-length I/O, and larger requests need the generic iomap direct
+ * I/O path.
+ *
+ * @dio_flags must not request IOMAP_DIO_FORCE_WAIT, IOMAP_DIO_PARTIAL, or
+ * IOMAP_DIO_BOUNCE: this path does not support forced waiting, partial direct
+ * I/O, or bouncing.  The range must also stay within i_size and encrypted
+ * inodes must use the generic iomap direct I/O path. IOMAP_DIO_NO_IOMAP_END is
+ * a fast-path optimization the caller can set if there is no work that needs to
+ * be done after a mapping.
+ *
+ * -ENOTBLK is the private sentinel returned by iomap_dio_simple() when it
+ * decides the request does not fit the fast path.  In that case we proceed to
+ * the generic __iomap_dio_rw() slow path.  Any other errno is a real result and
+ * is propagated as-is, in particular -EAGAIN for IOCB_NOWAIT must reach the
+ * caller.
+ */
+static ssize_t iomap_dio_simple(struct kiocb *iocb,
+                                struct iov_iter *iter, iomap_next_fn iomap_next,
+                                void *private, unsigned int dio_flags)
+{
+        struct inode *inode = file_inode(iocb->ki_filp);
+        size_t count = iov_iter_count(iter);
+        struct iomap_iter iomi = {
+                .inode                = inode,
+                .pos                = iocb->ki_pos,
+                .len                = count,
+                .flags                = IOMAP_DIRECT,
+                .private        = private,
+        };
+        ssize_t ret;
+
+        if (iocb->ki_flags & IOCB_NOWAIT)
+                iomi.flags |= IOMAP_NOWAIT;
+
+        ret = kiocb_write_and_wait(iocb, count);
+        if (ret)
+                return ret;
+
+        inode_dio_begin(inode);
+
+        ret = iomap_next(&iomi, &iomi.iomap, &iomi.srcmap);
+        if (ret <= 0) {
+                inode_dio_end(inode);
+                /* a zero return means no mapping, which should never happen */
+                return ret ? ret : -EFAULT;
+        }
+
+        return __iomap_dio_simple(iocb, iter, &iomi, iomap_next, dio_flags);
+}
+
+ssize_t iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter,
+                     iomap_next_fn iomap_next, const struct iomap_dio_ops *dops,
+                     unsigned int dio_flags, void *private, size_t done_before)
+{
+        struct iomap_dio *dio;
+        ssize_t ret;
+
+        if (iomap_dio_simple_supported(iocb, iter, dops, dio_flags, done_before)) {
+                ret = iomap_dio_simple(iocb, iter, iomap_next, private,
+                                       dio_flags);
+                if (ret != -ENOTBLK)
+                        return ret;
+        }
+
+        dio = __iomap_dio_rw(iocb, iter, iomap_next, dops, dio_flags,
+                             private, done_before);
+        if (IS_ERR_OR_NULL(dio))
+                return PTR_ERR_OR_ZERO(dio);
+        return iomap_dio_complete(dio);
+}
+EXPORT_SYMBOL_GPL(iomap_dio_rw);
 
 static int __init iomap_dio_init(void)
 {
diff --git a/include/linux/iomap.h b/include/linux/iomap.h
index 6213a3b1a0b93..d6028722c54b2 100644
--- a/include/linux/iomap.h
+++ b/include/linux/iomap.h
@@ -615,126 +615,9 @@ struct iomap_dio *__iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter,
 ssize_t iomap_dio_complete(struct iomap_dio *dio);
 void iomap_dio_bio_end_io(struct bio *bio);
 
-static inline bool
-iomap_dio_simple_supported(struct kiocb *iocb, struct iov_iter *iter,
-                           const struct iomap_dio_ops *dops,
-                           unsigned int dio_flags, size_t done_before)
-{
-        struct inode *inode = file_inode(iocb->ki_filp);
-        size_t count = iov_iter_count(iter);
-
-        if (dops || done_before)
-                return false;
-        if (iov_iter_rw(iter) != READ)
-                return false;
-        if (!count)
-                return false;
-        if (count > inode->i_sb->s_blocksize)
-                return false;
-        if (dio_flags & (IOMAP_DIO_FORCE_WAIT | IOMAP_DIO_PARTIAL |
-                         IOMAP_DIO_BOUNCE))
-                return false;
-        if (iocb->ki_pos + count > i_size_read(inode))
-                return false;
-        if (IS_ENCRYPTED(inode))
-                return false;
-        return true;
-}
-
-ssize_t __iomap_dio_simple(struct kiocb *iocb, struct iov_iter *iter,
-                struct iomap_iter *iomi, iomap_next_fn iomap_next,
-                unsigned int dio_flags);
-
-/*
- * Fast path for small, block-aligned direct I/Os that map to a single
- * contiguous on-disk extent.
- *
- * iomap_dio_simple_supported() enforces the cheap up-front constraints before
- * entering this path.
- *
- * @dops must be NULL: a non-NULL @dops means the caller wants its
- * ->end_io / ->submit_io hooks invoked, and in particular wants its bios to be
- * allocated from the filesystem-private @dops->bio_set (whose front_pad sizes a
- * filesystem-private wrapper around the bio).  The fast path instead allocates
- * from the shared iomap_dio_simple_pool, whose front_pad matches struct
- * iomap_dio_simple; the two wrappers are not interchangeable, so we must fall
- * back to __iomap_dio_rw() in that case.
- *
- * @done_before must be zero: a non-zero caller-accumulated residual cannot be
- * carried through a single-bio inline completion.
- *
- * @iter must describe a non-empty READ no larger than the inode block size:
- * writes, zero-length I/O, and larger requests need the generic iomap direct
- * I/O path.
- *
- * @dio_flags must not request IOMAP_DIO_FORCE_WAIT, IOMAP_DIO_PARTIAL, or
- * IOMAP_DIO_BOUNCE: this path does not support forced waiting, partial direct
- * I/O, or bouncing.  The range must also stay within i_size and encrypted
- * inodes must use the generic iomap direct I/O path. IOMAP_DIO_NO_IOMAP_END is
- * a fast-path optimization the caller can set if there is no work that needs to
- * be done after a mapping.
- *
- * -ENOTBLK is the private sentinel returned by iomap_dio_simple() when it
- * decides the request does not fit the fast path.  In that case we proceed to
- * the generic __iomap_dio_rw() slow path.  Any other errno is a real result and
- * is propagated as-is, in particular -EAGAIN for IOCB_NOWAIT must reach the
- * caller.
- */
-static __always_inline ssize_t
-iomap_dio_simple(struct kiocb *iocb, struct iov_iter *iter,
-                iomap_next_fn iomap_next, void *private, unsigned int dio_flags)
-{
-        struct inode *inode = file_inode(iocb->ki_filp);
-        size_t count = iov_iter_count(iter);
-        struct iomap_iter iomi = {
-                .inode                = inode,
-                .pos                = iocb->ki_pos,
-                .len                = count,
-                .flags                = IOMAP_DIRECT,
-                .private        = private,
-        };
-        ssize_t ret;
-
-        if (iocb->ki_flags & IOCB_NOWAIT)
-                iomi.flags |= IOMAP_NOWAIT;
-
-        ret = kiocb_write_and_wait(iocb, count);
-        if (ret)
-                return ret;
-
-        inode_dio_begin(inode);
-
-        ret = iomap_next(&iomi, &iomi.iomap, &iomi.srcmap);
-        if (ret <= 0) {
-                inode_dio_end(inode);
-                /* a zero return means no mapping, which should never happen */
-                return ret ? ret : -EFAULT;
-        }
-
-        return __iomap_dio_simple(iocb, iter, &iomi, iomap_next, dio_flags);
-}
-
-static __always_inline ssize_t
-iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter, iomap_next_fn iomap_next,
-                const struct iomap_dio_ops *dops, unsigned int dio_flags,
-                void *private, size_t done_before)
-{
-        struct iomap_dio *dio;
-        ssize_t ret;
-
-        if (iomap_dio_simple_supported(iocb, iter, dops, dio_flags, done_before)) {
-                ret = iomap_dio_simple(iocb, iter, iomap_next, private,
-                                dio_flags);
-                if (ret != -ENOTBLK)
-                        return ret;
-        }
-
-        dio = __iomap_dio_rw(iocb, iter, iomap_next, dops, dio_flags, private,
-                             done_before);
-        if (IS_ERR_OR_NULL(dio))
-                return PTR_ERR_OR_ZERO(dio);
-        return iomap_dio_complete(dio);
-}
+ssize_t iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter,
+                     iomap_next_fn iomap_next, const struct iomap_dio_ops *dops,
+                     unsigned int dio_flags, void *private, size_t done_before);
 
 #ifdef CONFIG_SWAP
 struct file;
-- 

> 
> I'm seeing that ext4_iomap_begin is ~1570 bytes vs.
> xfs_read_iomap_begin is ~635 bytes. It looks like though
> ext4_iomap_begin is a combined read + write begin, whereas xfs's is a
> dedicated read-only begin. If we separate ext4_iomap_begin into
> dedicated read vs write begins, then I think this gets
> ext4_read_iomap_begin down to ~230 bytes.
> 
> Would you be able to test if this improves the ext4 performance you're seeing?:
> 
> change #1: separate out ext4_iomap_begin into ext4_read/write_iomap_begin
> 
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index 4c30dd8dbec7..54cdecbc7539 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -3771,8 +3771,46 @@ static int ext4_iomap_alloc(struct inode
> *inode, struct ext4_map_blocks *map,
>  }
> 
> 
> -static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> -               unsigned flags, struct iomap *iomap, struct iomap *srcmap)
> +static int ext4_read_iomap_begin(struct inode *inode, loff_t offset,
> +               loff_t length, unsigned int flags, struct iomap *iomap,
> +               struct iomap *srcmap)
> +{
> +       int ret;
> +       struct ext4_map_blocks map;
> +       u8 blkbits = inode->i_blkbits;
> +
> +       if ((offset >> blkbits) > EXT4_MAX_LOGICAL_BLOCK)
> +               return -EINVAL;
> +
> +       if (WARN_ON_ONCE(ext4_has_inline_data(inode)))
> +               return -ERANGE;
> +
> +       /*
> +        * Calculate the first and last logical blocks respectively.
> +        */
> +       map.m_lblk = offset >> blkbits;
> +       map.m_len = min_t(loff_t, (offset + length - 1) >> blkbits,
> +                         EXT4_MAX_LOGICAL_BLOCK) - map.m_lblk + 1;
> +
> +       ret = ext4_map_blocks(NULL, inode, &map, 0);
> +       if (ret < 0)
> +               return ret;
> +
> +       /*
> +        * When inline encryption is enabled, sometimes I/O to an encrypted file
> +        * has to be broken up to guarantee DUN contiguity.  Handle this by
> +        * limiting the length of the mapping returned.
> +        */
> +       map.m_len = fscrypt_limit_io_blocks(inode, map.m_lblk, map.m_len);
> +
> +       ext4_set_iomap(inode, iomap, &map, offset, length, flags);
> +
> +       return 0;
> +}
> +
> +static int ext4_write_iomap_begin(struct inode *inode, loff_t offset,
> +               loff_t length, unsigned int flags, struct iomap *iomap,
> +               struct iomap *srcmap)
>  {
>         int ret;
>         struct ext4_map_blocks map;
> @@ -3793,37 +3831,33 @@ static int ext4_iomap_begin(struct inode
> *inode, loff_t offset, loff_t length,
>                           EXT4_MAX_LOGICAL_BLOCK) - map.m_lblk + 1;
>         orig_mlen = map.m_len;
> 
> -       if (flags & IOMAP_WRITE) {
> +       /*
> +        * We check here if the blocks are already allocated, then we
> +        * don't need to start a journal txn and we can directly return
> +        * the mapping information. This could boost performance
> +        * especially in multi-threaded overwrite requests.
> +        */
> +       if (offset + length <= i_size_read(inode)) {
> +               ret = ext4_map_blocks(NULL, inode, &map, 0);
>                 /*
> -                * We check here if the blocks are already allocated, then we
> -                * don't need to start a journal txn and we can directly return
> -                * the mapping information. This could boost performance
> -                * especially in multi-threaded overwrite requests.
> +                * For DAX we convert extents to initialized ones before
> +                * copying the data, otherwise we do it after I/O so
> +                * there's no need to call into ext4_iomap_alloc().
>                  */
> -               if (offset + length <= i_size_read(inode)) {
> -                       ret = ext4_map_blocks(NULL, inode, &map, 0);
> +               if ((map.m_flags & EXT4_MAP_MAPPED) ||
> +                   (!(flags & IOMAP_DAX) &&
> +                    (map.m_flags & EXT4_MAP_UNWRITTEN))) {
>                         /*
> -                        * For DAX we convert extents to initialized ones before
> -                        * copying the data, otherwise we do it after I/O so
> -                        * there's no need to call into ext4_iomap_alloc().
> +                        * For atomic writes the entire requested
> +                        * length should be mapped.
>                          */
> -                       if ((map.m_flags & EXT4_MAP_MAPPED) ||
> -                           (!(flags & IOMAP_DAX) &&
> -                            (map.m_flags & EXT4_MAP_UNWRITTEN))) {
> -                               /*
> -                                * For atomic writes the entire requested
> -                                * length should be mapped.
> -                                */
> -                               if (ret == orig_mlen ||
> -                                   (!(flags & IOMAP_ATOMIC) && ret > 0))
> -                                       goto out;
> -                       }
> -                       map.m_len = orig_mlen;
> +                       if (ret == orig_mlen ||
> +                           (!(flags & IOMAP_ATOMIC) && ret > 0))
> +                               goto out;
>                 }
> -               ret = ext4_iomap_alloc(inode, &map, flags);
> -       } else {
> -               ret = ext4_map_blocks(NULL, inode, &map, 0);
> +               map.m_len = orig_mlen;
>         }
> +       ret = ext4_iomap_alloc(inode, &map, flags);
> 
>         if (ret < 0)
>                 return ret;
> @@ -3850,6 +3884,16 @@ static int ext4_iomap_begin(struct inode
> *inode, loff_t offset, loff_t length,
>         return 0;
>  }
> 
> +static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> +               unsigned flags, struct iomap *iomap, struct iomap *srcmap)
> +{
> +       if (flags & IOMAP_WRITE)
> +               return ext4_write_iomap_begin(inode, offset, length, flags,
> +                                             iomap, srcmap);
> +       return ext4_read_iomap_begin(inode, offset, length, flags, iomap,
> +                                    srcmap);
> +}
> +
>  int ext4_iomap_next(const struct iomap_iter *iter, struct iomap *iomap,
>                            struct iomap *srcmap)
>  {
> 
> 
> change #2: use ext4_read_iomap_next in iomap_dio_rw
> 
> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> index 755fde1baf03..bef2851d5466 100644
> --- a/fs/ext4/ext4.h
> +++ b/fs/ext4/ext4.h
> @@ -4006,6 +4006,8 @@ static inline void
> ext4_clear_io_unwritten_flag(ext4_io_end_t *io_end)
> 
>  int ext4_iomap_next(const struct iomap_iter *iter, struct iomap *iomap,
>                 struct iomap *srcmap);
> +int ext4_read_iomap_next(const struct iomap_iter *iter, struct iomap *iomap,
> +               struct iomap *srcmap);
>  int ext4_iomap_next_report(const struct iomap_iter *iter, struct iomap *iomap,
>                 struct iomap *srcmap);
> 
> diff --git a/fs/ext4/file.c b/fs/ext4/file.c
> index bf0c18cf1017..48c6a76a16a1 100644
> --- a/fs/ext4/file.c
> +++ b/fs/ext4/file.c
> @@ -91,7 +91,7 @@ static ssize_t ext4_dio_read_iter(struct kiocb
> *iocb, struct iov_iter *to)
>                 return generic_file_read_iter(iocb, to);
>         }
> 
> -       ret = iomap_dio_rw(iocb, to, ext4_iomap_next, NULL,
> +       ret = iomap_dio_rw(iocb, to, ext4_read_iomap_next, NULL,
>                            IOMAP_DIO_NO_IOMAP_END, NULL, 0);
>         inode_unlock_shared(inode);
> 
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index 54cdecbc7539..6e7c77276d58 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -3771,9 +3771,9 @@ static int ext4_iomap_alloc(struct inode *inode,
> struct ext4_map_blocks *map,
>  }
> 
> 
> -static int ext4_read_iomap_begin(struct inode *inode, loff_t offset,
> -               loff_t length, unsigned int flags, struct iomap *iomap,
> -               struct iomap *srcmap)
> +static __always_inline int ext4_read_iomap_begin(struct inode *inode,
> +               loff_t offset, loff_t length, unsigned int flags,
> +               struct iomap *iomap, struct iomap *srcmap)
>  {
>         int ret;
>         struct ext4_map_blocks map;
> @@ -3900,6 +3900,12 @@ int ext4_iomap_next(const struct iomap_iter
> *iter, struct iomap *iomap,
>         return iomap_process(iter, iomap, srcmap, ext4_iomap_begin, NULL);
>  }
> 
> +int ext4_read_iomap_next(const struct iomap_iter *iter, struct iomap *iomap,
> +                          struct iomap *srcmap)
> +{
> +       return iomap_process(iter, iomap, srcmap, ext4_read_iomap_begin, NULL);
> +}
> +
>  static int ext4_iomap_begin_report(struct inode *inode, loff_t offset,
>                                    loff_t length, unsigned int flags,
>                                    struct iomap *iomap, struct iomap *srcmap)
> 
> The github link for the changes are in [1].
> 
> Thanks for your patience with this!
> 
> [1] https://github.com/joannekoong/linux/commits/iomap_simple_dio_iomap_next_ext4/
>