162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2016 Trond Myklebust
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * I/O and data path helper functionality.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/types.h>
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/bitops.h>
1162306a36Sopenharmony_ci#include <linux/rwsem.h>
1262306a36Sopenharmony_ci#include <linux/fs.h>
1362306a36Sopenharmony_ci#include <linux/nfs_fs.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include "internal.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci/* Call with exclusively locked inode->i_rwsem */
1862306a36Sopenharmony_cistatic void nfs_block_o_direct(struct nfs_inode *nfsi, struct inode *inode)
1962306a36Sopenharmony_ci{
2062306a36Sopenharmony_ci	if (test_bit(NFS_INO_ODIRECT, &nfsi->flags)) {
2162306a36Sopenharmony_ci		clear_bit(NFS_INO_ODIRECT, &nfsi->flags);
2262306a36Sopenharmony_ci		inode_dio_wait(inode);
2362306a36Sopenharmony_ci	}
2462306a36Sopenharmony_ci}
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/**
2762306a36Sopenharmony_ci * nfs_start_io_read - declare the file is being used for buffered reads
2862306a36Sopenharmony_ci * @inode: file inode
2962306a36Sopenharmony_ci *
3062306a36Sopenharmony_ci * Declare that a buffered read operation is about to start, and ensure
3162306a36Sopenharmony_ci * that we block all direct I/O.
3262306a36Sopenharmony_ci * On exit, the function ensures that the NFS_INO_ODIRECT flag is unset,
3362306a36Sopenharmony_ci * and holds a shared lock on inode->i_rwsem to ensure that the flag
3462306a36Sopenharmony_ci * cannot be changed.
3562306a36Sopenharmony_ci * In practice, this means that buffered read operations are allowed to
3662306a36Sopenharmony_ci * execute in parallel, thanks to the shared lock, whereas direct I/O
3762306a36Sopenharmony_ci * operations need to wait to grab an exclusive lock in order to set
3862306a36Sopenharmony_ci * NFS_INO_ODIRECT.
3962306a36Sopenharmony_ci * Note that buffered writes and truncates both take a write lock on
4062306a36Sopenharmony_ci * inode->i_rwsem, meaning that those are serialised w.r.t. the reads.
4162306a36Sopenharmony_ci */
4262306a36Sopenharmony_civoid
4362306a36Sopenharmony_cinfs_start_io_read(struct inode *inode)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct nfs_inode *nfsi = NFS_I(inode);
4662306a36Sopenharmony_ci	/* Be an optimist! */
4762306a36Sopenharmony_ci	down_read(&inode->i_rwsem);
4862306a36Sopenharmony_ci	if (test_bit(NFS_INO_ODIRECT, &nfsi->flags) == 0)
4962306a36Sopenharmony_ci		return;
5062306a36Sopenharmony_ci	up_read(&inode->i_rwsem);
5162306a36Sopenharmony_ci	/* Slow path.... */
5262306a36Sopenharmony_ci	down_write(&inode->i_rwsem);
5362306a36Sopenharmony_ci	nfs_block_o_direct(nfsi, inode);
5462306a36Sopenharmony_ci	downgrade_write(&inode->i_rwsem);
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci/**
5862306a36Sopenharmony_ci * nfs_end_io_read - declare that the buffered read operation is done
5962306a36Sopenharmony_ci * @inode: file inode
6062306a36Sopenharmony_ci *
6162306a36Sopenharmony_ci * Declare that a buffered read operation is done, and release the shared
6262306a36Sopenharmony_ci * lock on inode->i_rwsem.
6362306a36Sopenharmony_ci */
6462306a36Sopenharmony_civoid
6562306a36Sopenharmony_cinfs_end_io_read(struct inode *inode)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	up_read(&inode->i_rwsem);
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci/**
7162306a36Sopenharmony_ci * nfs_start_io_write - declare the file is being used for buffered writes
7262306a36Sopenharmony_ci * @inode: file inode
7362306a36Sopenharmony_ci *
7462306a36Sopenharmony_ci * Declare that a buffered read operation is about to start, and ensure
7562306a36Sopenharmony_ci * that we block all direct I/O.
7662306a36Sopenharmony_ci */
7762306a36Sopenharmony_civoid
7862306a36Sopenharmony_cinfs_start_io_write(struct inode *inode)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	down_write(&inode->i_rwsem);
8162306a36Sopenharmony_ci	nfs_block_o_direct(NFS_I(inode), inode);
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci/**
8562306a36Sopenharmony_ci * nfs_end_io_write - declare that the buffered write operation is done
8662306a36Sopenharmony_ci * @inode: file inode
8762306a36Sopenharmony_ci *
8862306a36Sopenharmony_ci * Declare that a buffered write operation is done, and release the
8962306a36Sopenharmony_ci * lock on inode->i_rwsem.
9062306a36Sopenharmony_ci */
9162306a36Sopenharmony_civoid
9262306a36Sopenharmony_cinfs_end_io_write(struct inode *inode)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	up_write(&inode->i_rwsem);
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci/* Call with exclusively locked inode->i_rwsem */
9862306a36Sopenharmony_cistatic void nfs_block_buffered(struct nfs_inode *nfsi, struct inode *inode)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	if (!test_bit(NFS_INO_ODIRECT, &nfsi->flags)) {
10162306a36Sopenharmony_ci		set_bit(NFS_INO_ODIRECT, &nfsi->flags);
10262306a36Sopenharmony_ci		nfs_sync_mapping(inode->i_mapping);
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci/**
10762306a36Sopenharmony_ci * nfs_start_io_direct - declare the file is being used for direct i/o
10862306a36Sopenharmony_ci * @inode: file inode
10962306a36Sopenharmony_ci *
11062306a36Sopenharmony_ci * Declare that a direct I/O operation is about to start, and ensure
11162306a36Sopenharmony_ci * that we block all buffered I/O.
11262306a36Sopenharmony_ci * On exit, the function ensures that the NFS_INO_ODIRECT flag is set,
11362306a36Sopenharmony_ci * and holds a shared lock on inode->i_rwsem to ensure that the flag
11462306a36Sopenharmony_ci * cannot be changed.
11562306a36Sopenharmony_ci * In practice, this means that direct I/O operations are allowed to
11662306a36Sopenharmony_ci * execute in parallel, thanks to the shared lock, whereas buffered I/O
11762306a36Sopenharmony_ci * operations need to wait to grab an exclusive lock in order to clear
11862306a36Sopenharmony_ci * NFS_INO_ODIRECT.
11962306a36Sopenharmony_ci * Note that buffered writes and truncates both take a write lock on
12062306a36Sopenharmony_ci * inode->i_rwsem, meaning that those are serialised w.r.t. O_DIRECT.
12162306a36Sopenharmony_ci */
12262306a36Sopenharmony_civoid
12362306a36Sopenharmony_cinfs_start_io_direct(struct inode *inode)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	struct nfs_inode *nfsi = NFS_I(inode);
12662306a36Sopenharmony_ci	/* Be an optimist! */
12762306a36Sopenharmony_ci	down_read(&inode->i_rwsem);
12862306a36Sopenharmony_ci	if (test_bit(NFS_INO_ODIRECT, &nfsi->flags) != 0)
12962306a36Sopenharmony_ci		return;
13062306a36Sopenharmony_ci	up_read(&inode->i_rwsem);
13162306a36Sopenharmony_ci	/* Slow path.... */
13262306a36Sopenharmony_ci	down_write(&inode->i_rwsem);
13362306a36Sopenharmony_ci	nfs_block_buffered(nfsi, inode);
13462306a36Sopenharmony_ci	downgrade_write(&inode->i_rwsem);
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci/**
13862306a36Sopenharmony_ci * nfs_end_io_direct - declare that the direct i/o operation is done
13962306a36Sopenharmony_ci * @inode: file inode
14062306a36Sopenharmony_ci *
14162306a36Sopenharmony_ci * Declare that a direct I/O operation is done, and release the shared
14262306a36Sopenharmony_ci * lock on inode->i_rwsem.
14362306a36Sopenharmony_ci */
14462306a36Sopenharmony_civoid
14562306a36Sopenharmony_cinfs_end_io_direct(struct inode *inode)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	up_read(&inode->i_rwsem);
14862306a36Sopenharmony_ci}
149