[RFC PATCH 3/8] fat: add iomap direct I/O support

From: Matheus Alves de Almeida

Date: Sat Oct 31 2026 - 12:47:49 EST


Replace the deprecated ->direct_IO() path with ->{read,write}_iter() and
iomap_dio_rw(). Add fat_file_open() to advertise direct I/O support.

Signed-off-by: Matheus Alves de Almeida <matheus.aalmeida@xxxxxxxxxxxx>
---
fs/fat/Kconfig | 1 -
fs/fat/fat.h | 1 +
fs/fat/file.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++-
fs/fat/inode.c | 36 ---------------
fs/fat/iomap.c | 24 ++++++++++
5 files changed, 142 insertions(+), 39 deletions(-)

diff --git a/fs/fat/Kconfig b/fs/fat/Kconfig
index d31d7866ce19..7989c6bf0fb6 100644
--- a/fs/fat/Kconfig
+++ b/fs/fat/Kconfig
@@ -4,7 +4,6 @@ config FAT_FS
select BUFFER_HEAD
select FS_IOMAP
select NLS
- select LEGACY_DIRECT_IO
help
If you want to use one of the FAT-based file systems (the MS-DOS and
VFAT (Windows 95) file systems), then you must say Y or M here
diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index 81c454f8a78a..e14d6d9a6ac1 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -492,6 +492,7 @@ extern const struct export_operations fat_export_ops_nostale;
/* fat/iomap.c */
extern const struct iomap_ops fat_iomap_ops;
extern const struct iomap_ops fat_iomap_bmap_ops;
+extern const struct iomap_dio_ops fat_iomap_write_dio_ops;

/* helper for printk */
typedef unsigned long long llu;
diff --git a/fs/fat/file.c b/fs/fat/file.c
index c27d4e7f698c..177a26186ddc 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -201,11 +201,126 @@ int fat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
return blkdev_issue_flush(inode->i_sb->s_bdev);
}

+static int fat_file_open(struct inode *inode, struct file *file)
+{
+ int err;
+
+ err = generic_file_open(inode, file);
+ if (err)
+ return err;
+
+ file->f_mode |= FMODE_CAN_ODIRECT;
+ return 0;
+}
+
+static ssize_t fat_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
+{
+ struct inode *inode = file_inode(iocb->ki_filp);
+ ssize_t ret;
+
+ if ((iocb->ki_flags & IOCB_DIRECT) == 0)
+ return generic_file_read_iter(iocb, iter);
+
+ inode_lock_shared(inode);
+ file_accessed(iocb->ki_filp);
+ ret = iomap_dio_rw(iocb, iter, &fat_iomap_ops, NULL, 0, NULL, 0);
+ inode_unlock_shared(inode);
+ return ret;
+}
+
+static ssize_t fat_file_dio_write_iter(struct kiocb *iocb,
+ struct iov_iter *iter)
+{
+ struct inode *inode = file_inode(iocb->ki_filp);
+ struct msdos_inode_info *mi = MSDOS_I(inode);
+ unsigned int dio_flags = 0;
+ ssize_t ret;
+
+ inode_lock(inode);
+ ret = generic_write_checks(iocb, iter);
+ if (ret <= 0)
+ goto out;
+
+ /* We do not support DIO writes which would need zeroing */
+ if (iocb->ki_pos + iov_iter_count(iter) > mi->mmu_private) {
+ ret = -ENOTBLK;
+ goto out;
+ }
+
+ ret = file_modified(iocb->ki_filp);
+ if (ret)
+ goto out;
+
+ /* Wait under the inode lock for size updates and partial-block zeroing. */
+ if (iocb->ki_pos + iov_iter_count(iter) > i_size_read(inode) ||
+ !IS_ALIGNED(iocb->ki_pos | iov_iter_alignment(iter),
+ i_blocksize(inode)))
+ dio_flags |= IOMAP_DIO_FORCE_WAIT;
+
+ ret = iomap_dio_rw(iocb, iter, &fat_iomap_ops,
+ &fat_iomap_write_dio_ops, dio_flags, NULL, 0);
+out:
+ inode_unlock(inode);
+ return ret;
+}
+
+static ssize_t fat_file_write_iter_buffered_fallback(struct kiocb *iocb,
+ struct iov_iter *iter)
+{
+ ssize_t ret;
+ loff_t end, offset;
+ int err;
+
+ iocb->ki_flags &= ~IOCB_DIRECT;
+ ret = generic_file_write_iter(iocb, iter);
+ if (ret <= 0)
+ return ret;
+
+ offset = iocb->ki_pos - ret;
+ end = iocb->ki_pos - 1;
+ err = filemap_write_and_wait_range(iocb->ki_filp->f_mapping, offset,
+ end);
+ if (err)
+ return err;
+
+ invalidate_mapping_pages(iocb->ki_filp->f_mapping,
+ offset >> PAGE_SHIFT,
+ end >> PAGE_SHIFT);
+
+ return ret;
+}
+
+static ssize_t fat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
+{
+ ssize_t ret;
+
+ if ((iocb->ki_flags & IOCB_DIRECT) == 0)
+ return generic_file_write_iter(iocb, iter);
+
+ ret = fat_file_dio_write_iter(iocb, iter);
+ if (ret == -ENOTBLK)
+ ret = 0;
+ else if (ret < 0)
+ return ret;
+
+ /* Fall back to buffered I/O if we could not do DIO */
+ if (iov_iter_count(iter)) {
+ ssize_t done;
+
+ done = fat_file_write_iter_buffered_fallback(iocb, iter);
+ if (done < 0)
+ return done;
+ ret += done;
+ }
+
+ return ret;
+}

const struct file_operations fat_file_operations = {
+ .open = fat_file_open,
.llseek = generic_file_llseek,
- .read_iter = generic_file_read_iter,
- .write_iter = generic_file_write_iter,
+ .read_iter = fat_file_read_iter,
+ .write_iter = fat_file_write_iter,
.mmap_prepare = generic_file_mmap_prepare,
.release = fat_file_release,
.unlocked_ioctl = fat_generic_ioctl,
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index 3885ddb916bd..953706aab68b 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -253,41 +253,6 @@ static int fat_write_end(const struct kiocb *iocb,
return err;
}

-static ssize_t fat_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
-{
- struct file *file = iocb->ki_filp;
- struct address_space *mapping = file->f_mapping;
- struct inode *inode = mapping->host;
- size_t count = iov_iter_count(iter);
- loff_t offset = iocb->ki_pos;
- ssize_t ret;
-
- if (iov_iter_rw(iter) == WRITE) {
- /*
- * FIXME: blockdev_direct_IO() doesn't use ->write_begin(),
- * so we need to update the ->mmu_private to block boundary.
- *
- * But we must fill the remaining area or hole by nul for
- * updating ->mmu_private.
- *
- * Return 0, and fallback to normal buffered write.
- */
- loff_t size = offset + count;
- if (MSDOS_I(inode)->mmu_private < size)
- return 0;
- }
-
- /*
- * FAT need to use the DIO_LOCKING for avoiding the race
- * condition of fat_get_block() and ->truncate().
- */
- ret = blockdev_direct_IO(iocb, inode, iter, fat_get_block);
- if (ret < 0 && iov_iter_rw(iter) == WRITE)
- fat_write_failed(mapping, offset + count);
-
- return ret;
-}
-
static sector_t _fat_bmap(struct address_space *mapping, sector_t block)
{
sector_t blocknr;
@@ -328,7 +293,6 @@ static const struct address_space_operations fat_aops = {
.writepages = fat_writepages,
.write_begin = fat_write_begin,
.write_end = fat_write_end,
- .direct_IO = fat_direct_IO,
.bmap = _fat_bmap,
.migrate_folio = buffer_migrate_folio,
.swap_activate = fat_swap_activate,
diff --git a/fs/fat/iomap.c b/fs/fat/iomap.c
index 7977147889bb..8a697deba9d4 100644
--- a/fs/fat/iomap.c
+++ b/fs/fat/iomap.c
@@ -26,6 +26,10 @@ static int __fat_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
return 0;
}

+ /* Keep the same behavior of syncing metadata on direct writes */
+ if (flags & IOMAP_WRITE)
+ iomap->flags |= IOMAP_F_DIRTY;
+
/*
* We need to separate normal data clusters and
* uninitialized FALLOC_FL_KEEP_SIZE clusters.
@@ -93,3 +97,23 @@ static DEFINE_IOMAP_ITER_NEXT(fat_iomap_next_bmap, fat_iomap_begin_bmap);
const struct iomap_ops fat_iomap_bmap_ops = {
.iomap_next = fat_iomap_next_bmap
};
+
+static int fat_dio_write_end_io(struct kiocb *iocb, ssize_t size, int error,
+ unsigned int flags)
+{
+ struct inode *inode = file_inode(iocb->ki_filp);
+
+ if (error)
+ return error;
+
+ if (size && i_size_read(inode) < iocb->ki_pos + size) {
+ i_size_write(inode, iocb->ki_pos + size);
+ mark_inode_dirty(inode);
+ }
+
+ return 0;
+}
+
+const struct iomap_dio_ops fat_iomap_write_dio_ops = {
+ .end_io = fat_dio_write_end_io
+};
--
2.43.0