diff options
Diffstat (limited to 'fs/nfs/direct.c')
| -rw-r--r-- | fs/nfs/direct.c | 96 | 
1 files changed, 63 insertions, 33 deletions
diff --git a/fs/nfs/direct.c b/fs/nfs/direct.c index 9a4cbfc85d81..1ba385b7c90d 100644 --- a/fs/nfs/direct.c +++ b/fs/nfs/direct.c @@ -115,17 +115,28 @@ static inline int put_dreq(struct nfs_direct_req *dreq)   * @nr_segs: size of iovec array   *   * The presence of this routine in the address space ops vector means - * the NFS client supports direct I/O.  However, we shunt off direct - * read and write requests before the VFS gets them, so this method - * should never be called. + * the NFS client supports direct I/O. However, for most direct IO, we + * shunt off direct read and write requests before the VFS gets them, + * so this method is only ever called for swap.   */  ssize_t nfs_direct_IO(int rw, struct kiocb *iocb, const struct iovec *iov, loff_t pos, unsigned long nr_segs)  { +#ifndef CONFIG_NFS_SWAP  	dprintk("NFS: nfs_direct_IO (%s) off/no(%Ld/%lu) EINVAL\n",  			iocb->ki_filp->f_path.dentry->d_name.name,  			(long long) pos, nr_segs);  	return -EINVAL; +#else +	VM_BUG_ON(iocb->ki_left != PAGE_SIZE); +	VM_BUG_ON(iocb->ki_nbytes != PAGE_SIZE); + +	if (rw == READ || rw == KERNEL_READ) +		return nfs_file_direct_read(iocb, iov, nr_segs, pos, +				rw == READ ? true : false); +	return nfs_file_direct_write(iocb, iov, nr_segs, pos, +				rw == WRITE ? true : false); +#endif /* CONFIG_NFS_SWAP */  }  static void nfs_direct_release_pages(struct page **pages, unsigned int npages) @@ -303,7 +314,7 @@ static const struct nfs_pgio_completion_ops nfs_direct_read_completion_ops = {   */  static ssize_t nfs_direct_read_schedule_segment(struct nfs_pageio_descriptor *desc,  						const struct iovec *iov, -						loff_t pos) +						loff_t pos, bool uio)  {  	struct nfs_direct_req *dreq = desc->pg_dreq;  	struct nfs_open_context *ctx = dreq->ctx; @@ -331,12 +342,20 @@ static ssize_t nfs_direct_read_schedule_segment(struct nfs_pageio_descriptor *de  					  GFP_KERNEL);  		if (!pagevec)  			break; -		down_read(¤t->mm->mmap_sem); -		result = get_user_pages(current, current->mm, user_addr, +		if (uio) { +			down_read(¤t->mm->mmap_sem); +			result = get_user_pages(current, current->mm, user_addr,  					npages, 1, 0, pagevec, NULL); -		up_read(¤t->mm->mmap_sem); -		if (result < 0) -			break; +			up_read(¤t->mm->mmap_sem); +			if (result < 0) +				break; +		} else { +			WARN_ON(npages != 1); +			result = get_kernel_page(user_addr, 1, pagevec); +			if (WARN_ON(result != 1)) +				break; +		} +  		if ((unsigned)result < npages) {  			bytes = result * PAGE_SIZE;  			if (bytes <= pgbase) { @@ -386,21 +405,21 @@ static ssize_t nfs_direct_read_schedule_segment(struct nfs_pageio_descriptor *de  static ssize_t nfs_direct_read_schedule_iovec(struct nfs_direct_req *dreq,  					      const struct iovec *iov,  					      unsigned long nr_segs, -					      loff_t pos) +					      loff_t pos, bool uio)  {  	struct nfs_pageio_descriptor desc;  	ssize_t result = -EINVAL;  	size_t requested_bytes = 0;  	unsigned long seg; -	nfs_pageio_init_read(&desc, dreq->inode, +	NFS_PROTO(dreq->inode)->read_pageio_init(&desc, dreq->inode,  			     &nfs_direct_read_completion_ops);  	get_dreq(dreq);  	desc.pg_dreq = dreq;  	for (seg = 0; seg < nr_segs; seg++) {  		const struct iovec *vec = &iov[seg]; -		result = nfs_direct_read_schedule_segment(&desc, vec, pos); +		result = nfs_direct_read_schedule_segment(&desc, vec, pos, uio);  		if (result < 0)  			break;  		requested_bytes += result; @@ -426,7 +445,7 @@ static ssize_t nfs_direct_read_schedule_iovec(struct nfs_direct_req *dreq,  }  static ssize_t nfs_direct_read(struct kiocb *iocb, const struct iovec *iov, -			       unsigned long nr_segs, loff_t pos) +			       unsigned long nr_segs, loff_t pos, bool uio)  {  	ssize_t result = -ENOMEM;  	struct inode *inode = iocb->ki_filp->f_mapping->host; @@ -444,7 +463,7 @@ static ssize_t nfs_direct_read(struct kiocb *iocb, const struct iovec *iov,  	if (!is_sync_kiocb(iocb))  		dreq->iocb = iocb; -	result = nfs_direct_read_schedule_iovec(dreq, iov, nr_segs, pos); +	result = nfs_direct_read_schedule_iovec(dreq, iov, nr_segs, pos, uio);  	if (!result)  		result = nfs_direct_wait(dreq);  	NFS_I(inode)->read_io += result; @@ -460,7 +479,7 @@ static void nfs_inode_dio_write_done(struct inode *inode)  	inode_dio_done(inode);  } -#if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4) +#if IS_ENABLED(CONFIG_NFS_V3) || IS_ENABLED(CONFIG_NFS_V4)  static void nfs_direct_write_reschedule(struct nfs_direct_req *dreq)  {  	struct nfs_pageio_descriptor desc; @@ -478,12 +497,13 @@ static void nfs_direct_write_reschedule(struct nfs_direct_req *dreq)  	dreq->count = 0;  	get_dreq(dreq); -	nfs_pageio_init_write(&desc, dreq->inode, FLUSH_STABLE, +	NFS_PROTO(dreq->inode)->write_pageio_init(&desc, dreq->inode, FLUSH_STABLE,  			      &nfs_direct_write_completion_ops);  	desc.pg_dreq = dreq;  	list_for_each_entry_safe(req, tmp, &reqs, wb_list) {  		if (!nfs_pageio_add_request(&desc, req)) { +			nfs_list_remove_request(req);  			nfs_list_add_request(req, &failed);  			spin_lock(cinfo.lock);  			dreq->flags = 0; @@ -494,8 +514,11 @@ static void nfs_direct_write_reschedule(struct nfs_direct_req *dreq)  	}  	nfs_pageio_complete(&desc); -	while (!list_empty(&failed)) +	while (!list_empty(&failed)) { +		req = nfs_list_entry(failed.next); +		nfs_list_remove_request(req);  		nfs_unlock_and_release_request(req); +	}  	if (put_dreq(dreq))  		nfs_direct_write_complete(dreq, dreq->inode); @@ -606,7 +629,7 @@ static void nfs_direct_write_complete(struct nfs_direct_req *dreq, struct inode   */  static ssize_t nfs_direct_write_schedule_segment(struct nfs_pageio_descriptor *desc,  						 const struct iovec *iov, -						 loff_t pos) +						 loff_t pos, bool uio)  {  	struct nfs_direct_req *dreq = desc->pg_dreq;  	struct nfs_open_context *ctx = dreq->ctx; @@ -634,12 +657,19 @@ static ssize_t nfs_direct_write_schedule_segment(struct nfs_pageio_descriptor *d  		if (!pagevec)  			break; -		down_read(¤t->mm->mmap_sem); -		result = get_user_pages(current, current->mm, user_addr, -					npages, 0, 0, pagevec, NULL); -		up_read(¤t->mm->mmap_sem); -		if (result < 0) -			break; +		if (uio) { +			down_read(¤t->mm->mmap_sem); +			result = get_user_pages(current, current->mm, user_addr, +						npages, 0, 0, pagevec, NULL); +			up_read(¤t->mm->mmap_sem); +			if (result < 0) +				break; +		} else { +			WARN_ON(npages != 1); +			result = get_kernel_page(user_addr, 0, pagevec); +			if (WARN_ON(result != 1)) +				break; +		}  		if ((unsigned)result < npages) {  			bytes = result * PAGE_SIZE; @@ -770,7 +800,7 @@ static const struct nfs_pgio_completion_ops nfs_direct_write_completion_ops = {  static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,  					       const struct iovec *iov,  					       unsigned long nr_segs, -					       loff_t pos) +					       loff_t pos, bool uio)  {  	struct nfs_pageio_descriptor desc;  	struct inode *inode = dreq->inode; @@ -778,7 +808,7 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,  	size_t requested_bytes = 0;  	unsigned long seg; -	nfs_pageio_init_write(&desc, inode, FLUSH_COND_STABLE, +	NFS_PROTO(inode)->write_pageio_init(&desc, inode, FLUSH_COND_STABLE,  			      &nfs_direct_write_completion_ops);  	desc.pg_dreq = dreq;  	get_dreq(dreq); @@ -786,7 +816,7 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,  	for (seg = 0; seg < nr_segs; seg++) {  		const struct iovec *vec = &iov[seg]; -		result = nfs_direct_write_schedule_segment(&desc, vec, pos); +		result = nfs_direct_write_schedule_segment(&desc, vec, pos, uio);  		if (result < 0)  			break;  		requested_bytes += result; @@ -814,7 +844,7 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,  static ssize_t nfs_direct_write(struct kiocb *iocb, const struct iovec *iov,  				unsigned long nr_segs, loff_t pos, -				size_t count) +				size_t count, bool uio)  {  	ssize_t result = -ENOMEM;  	struct inode *inode = iocb->ki_filp->f_mapping->host; @@ -832,7 +862,7 @@ static ssize_t nfs_direct_write(struct kiocb *iocb, const struct iovec *iov,  	if (!is_sync_kiocb(iocb))  		dreq->iocb = iocb; -	result = nfs_direct_write_schedule_iovec(dreq, iov, nr_segs, pos); +	result = nfs_direct_write_schedule_iovec(dreq, iov, nr_segs, pos, uio);  	if (!result)  		result = nfs_direct_wait(dreq);  out_release: @@ -863,7 +893,7 @@ out:   * cache.   */  ssize_t nfs_file_direct_read(struct kiocb *iocb, const struct iovec *iov, -				unsigned long nr_segs, loff_t pos) +				unsigned long nr_segs, loff_t pos, bool uio)  {  	ssize_t retval = -EINVAL;  	struct file *file = iocb->ki_filp; @@ -888,7 +918,7 @@ ssize_t nfs_file_direct_read(struct kiocb *iocb, const struct iovec *iov,  	task_io_account_read(count); -	retval = nfs_direct_read(iocb, iov, nr_segs, pos); +	retval = nfs_direct_read(iocb, iov, nr_segs, pos, uio);  	if (retval > 0)  		iocb->ki_pos = pos + retval; @@ -919,7 +949,7 @@ out:   * is no atomic O_APPEND write facility in the NFS protocol.   */  ssize_t nfs_file_direct_write(struct kiocb *iocb, const struct iovec *iov, -				unsigned long nr_segs, loff_t pos) +				unsigned long nr_segs, loff_t pos, bool uio)  {  	ssize_t retval = -EINVAL;  	struct file *file = iocb->ki_filp; @@ -951,7 +981,7 @@ ssize_t nfs_file_direct_write(struct kiocb *iocb, const struct iovec *iov,  	task_io_account_write(count); -	retval = nfs_direct_write(iocb, iov, nr_segs, pos, count); +	retval = nfs_direct_write(iocb, iov, nr_segs, pos, count, uio);  	if (retval > 0) {  		struct inode *inode = mapping->host;  | 
