Re: [PATCH v6 3/3] iomap: add simple dio path for small direct I/O
From: changfengnan
Date: Wed Jul 08 2026 - 21:43:38 EST
> From: "Joanne Koong"<joannelkoong@xxxxxxxxx>
> Date: Thu, Jul 9, 2026, 09:40
> 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 Wed, Jul 8, 2026 at 1:42 AM changfengnan <changfengnan@xxxxxxxxxxxxx> wrote:
> >
> >
> > > 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.
> >
>
> Thanks for tracking this down, I'm glad it doesn't regress the fast
> path anymore.
>
> I'll put your name as the author of the patch, especially since you
> did all the benchmarking too. I think I just need your legal
> permission to add your Signed-off-by. Is that ok? I'll add a
> Suggested-by: for christoph since he came up with a lot of the ideas
> with IOMAP_DIO_NO_IOMAP_END, moving some of the iteration logic into
> iomap_process, etc. I'll carry this in v3 of the iomap_next series.
Sure.
>
> Thanks,
> Joanne
>
> > 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/
> > >
>