Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9817d207 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
* 'splice' of git://brick.kernel.dk/data/git/linux-2.6-block:
  [PATCH] vmsplice: allow user to pass in gift pages
  [PATCH] pipe: enable atomic copying of pipe data to/from user space
  [PATCH] splice: call handle_ra_miss() on failure to lookup page
  [PATCH] Add ->splice_read/splice_write to def_blk_fops
  [PATCH] pipe: introduce ->pin() buffer operation
  [PATCH] splice: fix bugs in pipe_to_file()
  [PATCH] splice: fix bugs with stealing regular pipe pages
parents cf105601 7afa6fd0
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1104,6 +1104,8 @@ const struct file_operations def_blk_fops = {
	.readv		= generic_file_readv,
	.writev		= generic_file_write_nolock,
	.sendfile	= generic_file_sendfile,
	.splice_read	= generic_file_splice_read,
	.splice_write	= generic_file_splice_write,
};

int ioctl_by_bdev(struct block_device *bdev, unsigned cmd, unsigned long arg)
+145 −39
Original line number Diff line number Diff line
@@ -55,7 +55,8 @@ void pipe_wait(struct pipe_inode_info *pipe)
}

static int
pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len)
pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len,
			int atomic)
{
	unsigned long copy;

@@ -64,8 +65,13 @@ pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len)
			iov++;
		copy = min_t(unsigned long, len, iov->iov_len);

		if (atomic) {
			if (__copy_from_user_inatomic(to, iov->iov_base, copy))
				return -EFAULT;
		} else {
			if (copy_from_user(to, iov->iov_base, copy))
				return -EFAULT;
		}
		to += copy;
		len -= copy;
		iov->iov_base += copy;
@@ -75,7 +81,8 @@ pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len)
}

static int
pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len)
pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len,
		      int atomic)
{
	unsigned long copy;

@@ -84,8 +91,13 @@ pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len)
			iov++;
		copy = min_t(unsigned long, len, iov->iov_len);

		if (atomic) {
			if (__copy_to_user_inatomic(iov->iov_base, from, copy))
				return -EFAULT;
		} else {
			if (copy_to_user(iov->iov_base, from, copy))
				return -EFAULT;
		}
		from += copy;
		len -= copy;
		iov->iov_base += copy;
@@ -94,13 +106,52 @@ pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len)
	return 0;
}

/*
 * Attempt to pre-fault in the user memory, so we can use atomic copies.
 * Returns the number of bytes not faulted in.
 */
static int iov_fault_in_pages_write(struct iovec *iov, unsigned long len)
{
	while (!iov->iov_len)
		iov++;

	while (len > 0) {
		unsigned long this_len;

		this_len = min_t(unsigned long, len, iov->iov_len);
		if (fault_in_pages_writeable(iov->iov_base, this_len))
			break;

		len -= this_len;
		iov++;
	}

	return len;
}

/*
 * Pre-fault in the user memory, so we can use atomic copies.
 */
static void iov_fault_in_pages_read(struct iovec *iov, unsigned long len)
{
	while (!iov->iov_len)
		iov++;

	while (len > 0) {
		unsigned long this_len;

		this_len = min_t(unsigned long, len, iov->iov_len);
		fault_in_pages_readable(iov->iov_base, this_len);
		len -= this_len;
		iov++;
	}
}

static void anon_pipe_buf_release(struct pipe_inode_info *pipe,
				  struct pipe_buffer *buf)
{
	struct page *page = buf->page;

	buf->flags &= ~PIPE_BUF_FLAG_STOLEN;

	/*
	 * If nobody else uses this page, and we don't already have a
	 * temporary page, let's keep track of it as a one-deep
@@ -112,38 +163,58 @@ static void anon_pipe_buf_release(struct pipe_inode_info *pipe,
		page_cache_release(page);
}

static void * anon_pipe_buf_map(struct file *file, struct pipe_inode_info *pipe,
				struct pipe_buffer *buf)
void *generic_pipe_buf_map(struct pipe_inode_info *pipe,
			   struct pipe_buffer *buf, int atomic)
{
	if (atomic) {
		buf->flags |= PIPE_BUF_FLAG_ATOMIC;
		return kmap_atomic(buf->page, KM_USER0);
	}

	return kmap(buf->page);
}

static void anon_pipe_buf_unmap(struct pipe_inode_info *pipe,
				struct pipe_buffer *buf)
void generic_pipe_buf_unmap(struct pipe_inode_info *pipe,
			    struct pipe_buffer *buf, void *map_data)
{
	if (buf->flags & PIPE_BUF_FLAG_ATOMIC) {
		buf->flags &= ~PIPE_BUF_FLAG_ATOMIC;
		kunmap_atomic(map_data, KM_USER0);
	} else
		kunmap(buf->page);
}

static int anon_pipe_buf_steal(struct pipe_inode_info *pipe,
			       struct pipe_buffer *buf)
{
	buf->flags |= PIPE_BUF_FLAG_STOLEN;
	struct page *page = buf->page;

	if (page_count(page) == 1) {
		lock_page(page);
		return 0;
	}

static void anon_pipe_buf_get(struct pipe_inode_info *info,
			      struct pipe_buffer *buf)
	return 1;
}

void generic_pipe_buf_get(struct pipe_inode_info *info, struct pipe_buffer *buf)
{
	page_cache_get(buf->page);
}

int generic_pipe_buf_pin(struct pipe_inode_info *info, struct pipe_buffer *buf)
{
	return 0;
}

static struct pipe_buf_operations anon_pipe_buf_ops = {
	.can_merge = 1,
	.map = anon_pipe_buf_map,
	.unmap = anon_pipe_buf_unmap,
	.map = generic_pipe_buf_map,
	.unmap = generic_pipe_buf_unmap,
	.pin = generic_pipe_buf_pin,
	.release = anon_pipe_buf_release,
	.steal = anon_pipe_buf_steal,
	.get = anon_pipe_buf_get,
	.get = generic_pipe_buf_get,
};

static ssize_t
@@ -174,22 +245,33 @@ pipe_readv(struct file *filp, const struct iovec *_iov,
			struct pipe_buf_operations *ops = buf->ops;
			void *addr;
			size_t chars = buf->len;
			int error;
			int error, atomic;

			if (chars > total_len)
				chars = total_len;

			addr = ops->map(filp, pipe, buf);
			if (IS_ERR(addr)) {
			error = ops->pin(pipe, buf);
			if (error) {
				if (!ret)
					ret = PTR_ERR(addr);
					error = ret;
				break;
			}
			error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars);
			ops->unmap(pipe, buf);

			atomic = !iov_fault_in_pages_write(iov, chars);
redo:
			addr = ops->map(pipe, buf, atomic);
			error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic);
			ops->unmap(pipe, buf, addr);
			if (unlikely(error)) {
				/*
				 * Just retry with the slow path if we failed.
				 */
				if (atomic) {
					atomic = 0;
					goto redo;
				}
				if (!ret)
					ret = -EFAULT;
					ret = error;
				break;
			}
			ret += chars;
@@ -293,21 +375,28 @@ pipe_writev(struct file *filp, const struct iovec *_iov,
		int offset = buf->offset + buf->len;

		if (ops->can_merge && offset + chars <= PAGE_SIZE) {
			int error, atomic = 1;
			void *addr;
			int error;

			addr = ops->map(filp, pipe, buf);
			if (IS_ERR(addr)) {
				error = PTR_ERR(addr);
			error = ops->pin(pipe, buf);
			if (error)
				goto out;
			}

			iov_fault_in_pages_read(iov, chars);
redo1:
			addr = ops->map(pipe, buf, atomic);
			error = pipe_iov_copy_from_user(offset + addr, iov,
							chars);
			ops->unmap(pipe, buf);
							chars, atomic);
			ops->unmap(pipe, buf, addr);
			ret = error;
			do_wakeup = 1;
			if (error)
			if (error) {
				if (atomic) {
					atomic = 0;
					goto redo1;
				}
				goto out;
			}
			buf->len += chars;
			total_len -= chars;
			ret = chars;
@@ -330,7 +419,8 @@ pipe_writev(struct file *filp, const struct iovec *_iov,
			int newbuf = (pipe->curbuf + bufs) & (PIPE_BUFFERS-1);
			struct pipe_buffer *buf = pipe->bufs + newbuf;
			struct page *page = pipe->tmp_page;
			int error;
			char *src;
			int error, atomic = 1;

			if (!page) {
				page = alloc_page(GFP_HIGHUSER);
@@ -350,11 +440,27 @@ pipe_writev(struct file *filp, const struct iovec *_iov,
			if (chars > total_len)
				chars = total_len;

			error = pipe_iov_copy_from_user(kmap(page), iov, chars);
			iov_fault_in_pages_read(iov, chars);
redo2:
			if (atomic)
				src = kmap_atomic(page, KM_USER0);
			else
				src = kmap(page);

			error = pipe_iov_copy_from_user(src, iov, chars,
							atomic);
			if (atomic)
				kunmap_atomic(src, KM_USER0);
			else
				kunmap(page);

			if (unlikely(error)) {
				if (atomic) {
					atomic = 0;
					goto redo2;
				}
				if (!ret)
					ret = -EFAULT;
					ret = error;
				break;
			}
			ret += chars;
+83 −83
Original line number Diff line number Diff line
@@ -78,7 +78,7 @@ static int page_cache_pipe_buf_steal(struct pipe_inode_info *info,
		return 1;
	}

	buf->flags |= PIPE_BUF_FLAG_STOLEN | PIPE_BUF_FLAG_LRU;
	buf->flags |= PIPE_BUF_FLAG_LRU;
	return 0;
}

@@ -87,11 +87,10 @@ static void page_cache_pipe_buf_release(struct pipe_inode_info *info,
{
	page_cache_release(buf->page);
	buf->page = NULL;
	buf->flags &= ~(PIPE_BUF_FLAG_STOLEN | PIPE_BUF_FLAG_LRU);
	buf->flags &= ~PIPE_BUF_FLAG_LRU;
}

static void *page_cache_pipe_buf_map(struct file *file,
				     struct pipe_inode_info *info,
static int page_cache_pipe_buf_pin(struct pipe_inode_info *info,
				   struct pipe_buffer *buf)
{
	struct page *page = buf->page;
@@ -118,64 +117,44 @@ static void *page_cache_pipe_buf_map(struct file *file,
		}

		/*
		 * Page is ok afterall, fall through to mapping.
		 * Page is ok afterall, we are done.
		 */
		unlock_page(page);
	}

	return kmap(page);
	return 0;
error:
	unlock_page(page);
	return ERR_PTR(err);
}

static void page_cache_pipe_buf_unmap(struct pipe_inode_info *info,
				      struct pipe_buffer *buf)
{
	kunmap(buf->page);
}

static void *user_page_pipe_buf_map(struct file *file,
				    struct pipe_inode_info *pipe,
				    struct pipe_buffer *buf)
{
	return kmap(buf->page);
}

static void user_page_pipe_buf_unmap(struct pipe_inode_info *pipe,
				     struct pipe_buffer *buf)
{
	kunmap(buf->page);
}

static void page_cache_pipe_buf_get(struct pipe_inode_info *info,
				    struct pipe_buffer *buf)
{
	page_cache_get(buf->page);
	return err;
}

static struct pipe_buf_operations page_cache_pipe_buf_ops = {
	.can_merge = 0,
	.map = page_cache_pipe_buf_map,
	.unmap = page_cache_pipe_buf_unmap,
	.map = generic_pipe_buf_map,
	.unmap = generic_pipe_buf_unmap,
	.pin = page_cache_pipe_buf_pin,
	.release = page_cache_pipe_buf_release,
	.steal = page_cache_pipe_buf_steal,
	.get = page_cache_pipe_buf_get,
	.get = generic_pipe_buf_get,
};

static int user_page_pipe_buf_steal(struct pipe_inode_info *pipe,
				    struct pipe_buffer *buf)
{
	if (!(buf->flags & PIPE_BUF_FLAG_GIFT))
		return 1;

	return 0;
}

static struct pipe_buf_operations user_page_pipe_buf_ops = {
	.can_merge = 0,
	.map = user_page_pipe_buf_map,
	.unmap = user_page_pipe_buf_unmap,
	.map = generic_pipe_buf_map,
	.unmap = generic_pipe_buf_unmap,
	.pin = generic_pipe_buf_pin,
	.release = page_cache_pipe_buf_release,
	.steal = user_page_pipe_buf_steal,
	.get = page_cache_pipe_buf_get,
	.get = generic_pipe_buf_get,
};

/*
@@ -210,6 +189,9 @@ static ssize_t splice_to_pipe(struct pipe_inode_info *pipe,
			buf->offset = spd->partial[page_nr].offset;
			buf->len = spd->partial[page_nr].len;
			buf->ops = spd->ops;
			if (spd->flags & SPLICE_F_GIFT)
				buf->flags |= PIPE_BUF_FLAG_GIFT;

			pipe->nrbufs++;
			page_nr++;
			ret += buf->len;
@@ -325,6 +307,12 @@ __generic_file_splice_read(struct file *in, loff_t *ppos,
		 */
		page = find_get_page(mapping, index);
		if (!page) {
			/*
			 * Make sure the read-ahead engine is notified
			 * about this failure.
			 */
			handle_ra_miss(mapping, &in->f_ra, index);

			/*
			 * page didn't exist, allocate one.
			 */
@@ -517,26 +505,16 @@ static int pipe_to_sendpage(struct pipe_inode_info *info,
{
	struct file *file = sd->file;
	loff_t pos = sd->pos;
	ssize_t ret;
	void *ptr;
	int more;

	/*
	 * Sub-optimal, but we are limited by the pipe ->map. We don't
	 * need a kmap'ed buffer here, we just want to make sure we
	 * have the page pinned if the pipe page originates from the
	 * page cache.
	 */
	ptr = buf->ops->map(file, info, buf);
	if (IS_ERR(ptr))
		return PTR_ERR(ptr);
	int ret, more;

	ret = buf->ops->pin(info, buf);
	if (!ret) {
		more = (sd->flags & SPLICE_F_MORE) || sd->len < sd->total_len;

	ret = file->f_op->sendpage(file, buf->page, buf->offset, sd->len,
				   &pos, more);
		ret = file->f_op->sendpage(file, buf->page, buf->offset,
					   sd->len, &pos, more);
	}

	buf->ops->unmap(info, buf);
	return ret;
}

@@ -569,15 +547,14 @@ static int pipe_to_file(struct pipe_inode_info *info, struct pipe_buffer *buf,
	unsigned int offset, this_len;
	struct page *page;
	pgoff_t index;
	char *src;
	int ret;

	/*
	 * make sure the data in this buffer is uptodate
	 */
	src = buf->ops->map(file, info, buf);
	if (IS_ERR(src))
		return PTR_ERR(src);
	ret = buf->ops->pin(info, buf);
	if (unlikely(ret))
		return ret;

	index = sd->pos >> PAGE_CACHE_SHIFT;
	offset = sd->pos & ~PAGE_CACHE_MASK;
@@ -587,9 +564,10 @@ static int pipe_to_file(struct pipe_inode_info *info, struct pipe_buffer *buf,
		this_len = PAGE_CACHE_SIZE - offset;

	/*
	 * Reuse buf page, if SPLICE_F_MOVE is set.
	 * Reuse buf page, if SPLICE_F_MOVE is set and we are doing a full
	 * page.
	 */
	if (sd->flags & SPLICE_F_MOVE) {
	if ((sd->flags & SPLICE_F_MOVE) && this_len == PAGE_CACHE_SIZE) {
		/*
		 * If steal succeeds, buf->page is now pruned from the vm
		 * side (LRU and page cache) and we can reuse it. The page
@@ -599,8 +577,12 @@ static int pipe_to_file(struct pipe_inode_info *info, struct pipe_buffer *buf,
			goto find_page;

		page = buf->page;
		if (add_to_page_cache(page, mapping, index, gfp_mask))
		if (add_to_page_cache(page, mapping, index, gfp_mask)) {
			unlock_page(page);
			goto find_page;
		}

		page_cache_get(page);

		if (!(buf->flags & PIPE_BUF_FLAG_LRU))
			lru_cache_add(page);
@@ -660,34 +642,36 @@ find_page:
	} else if (ret)
		goto out;

	if (!(buf->flags & PIPE_BUF_FLAG_STOLEN)) {
		char *dst = kmap_atomic(page, KM_USER0);
	if (buf->page != page) {
		/*
		 * Careful, ->map() uses KM_USER0!
		 */
		char *src = buf->ops->map(info, buf, 1);
		char *dst = kmap_atomic(page, KM_USER1);

		memcpy(dst + offset, src + buf->offset, this_len);
		flush_dcache_page(page);
		kunmap_atomic(dst, KM_USER0);
		kunmap_atomic(dst, KM_USER1);
		buf->ops->unmap(info, buf, src);
	}

	ret = mapping->a_ops->commit_write(file, page, offset, offset+this_len);
	if (ret == AOP_TRUNCATED_PAGE) {
		page_cache_release(page);
		goto find_page;
	} else if (ret)
		goto out;

	if (!ret) {
		/*
	 * Return the number of bytes written.
		 * Return the number of bytes written and mark page as
		 * accessed, we are now done!
		 */
		ret = this_len;
		mark_page_accessed(page);
		balance_dirty_pages_ratelimited(mapping);
	} else if (ret == AOP_TRUNCATED_PAGE) {
		page_cache_release(page);
		goto find_page;
	}
out:
	if (!(buf->flags & PIPE_BUF_FLAG_STOLEN))
	page_cache_release(page);

	unlock_page(page);
out_nomem:
	buf->ops->unmap(info, buf);
	return ret;
}

@@ -1095,7 +1079,7 @@ static long do_splice(struct file *in, loff_t __user *off_in,
 */
static int get_iovec_page_array(const struct iovec __user *iov,
				unsigned int nr_vecs, struct page **pages,
				struct partial_page *partial)
				struct partial_page *partial, int aligned)
{
	int buffers = 0, error = 0;

@@ -1135,6 +1119,15 @@ static int get_iovec_page_array(const struct iovec __user *iov,
		 * in the user pages.
		 */
		off = (unsigned long) base & ~PAGE_MASK;

		/*
		 * If asked for alignment, the offset must be zero and the
		 * length a multiple of the PAGE_SIZE.
		 */
		error = -EINVAL;
		if (aligned && (off || len & ~PAGE_MASK))
			break;

		npages = (off + len + PAGE_SIZE - 1) >> PAGE_SHIFT;
		if (npages > PIPE_BUFFERS - buffers)
			npages = PIPE_BUFFERS - buffers;
@@ -1228,7 +1221,8 @@ static long do_vmsplice(struct file *file, const struct iovec __user *iov,
	else if (unlikely(!nr_segs))
		return 0;

	spd.nr_pages = get_iovec_page_array(iov, nr_segs, pages, partial);
	spd.nr_pages = get_iovec_page_array(iov, nr_segs, pages, partial,
					    flags & SPLICE_F_GIFT);
	if (spd.nr_pages <= 0)
		return spd.nr_pages;

@@ -1336,6 +1330,12 @@ static int link_pipe(struct pipe_inode_info *ipipe,
				obuf = opipe->bufs + nbuf;
				*obuf = *ibuf;

				/*
				 * Don't inherit the gift flag, we need to
				 * prevent multiple steals of this page.
				 */
				obuf->flags &= ~PIPE_BUF_FLAG_GIFT;

				if (obuf->len > len)
					obuf->len = len;

+25 −4
Original line number Diff line number Diff line
@@ -5,8 +5,9 @@

#define PIPE_BUFFERS (16)

#define PIPE_BUF_FLAG_STOLEN	0x01
#define PIPE_BUF_FLAG_LRU	0x02
#define PIPE_BUF_FLAG_LRU	0x01	/* page is on the LRU */
#define PIPE_BUF_FLAG_ATOMIC	0x02	/* was atomically mapped */
#define PIPE_BUF_FLAG_GIFT	0x04	/* page is a gift */

struct pipe_buffer {
	struct page *page;
@@ -15,10 +16,23 @@ struct pipe_buffer {
	unsigned int flags;
};

/*
 * Note on the nesting of these functions:
 *
 * ->pin()
 *	->steal()
 *	...
 *	->map()
 *	...
 *	->unmap()
 *
 * That is, ->map() must be called on a pinned buffer, same goes for ->steal().
 */
struct pipe_buf_operations {
	int can_merge;
	void * (*map)(struct file *, struct pipe_inode_info *, struct pipe_buffer *);
	void (*unmap)(struct pipe_inode_info *, struct pipe_buffer *);
	void * (*map)(struct pipe_inode_info *, struct pipe_buffer *, int);
	void (*unmap)(struct pipe_inode_info *, struct pipe_buffer *, void *);
	int (*pin)(struct pipe_inode_info *, struct pipe_buffer *);
	void (*release)(struct pipe_inode_info *, struct pipe_buffer *);
	int (*steal)(struct pipe_inode_info *, struct pipe_buffer *);
	void (*get)(struct pipe_inode_info *, struct pipe_buffer *);
@@ -51,6 +65,12 @@ struct pipe_inode_info * alloc_pipe_info(struct inode * inode);
void free_pipe_info(struct inode * inode);
void __free_pipe_info(struct pipe_inode_info *);

/* Generic pipe buffer ops functions */
void *generic_pipe_buf_map(struct pipe_inode_info *, struct pipe_buffer *, int);
void generic_pipe_buf_unmap(struct pipe_inode_info *, struct pipe_buffer *, void *);
void generic_pipe_buf_get(struct pipe_inode_info *, struct pipe_buffer *);
int generic_pipe_buf_pin(struct pipe_inode_info *, struct pipe_buffer *);

/*
 * splice is tied to pipes as a transport (at least for now), so we'll just
 * add the splice flags here.
@@ -60,6 +80,7 @@ void __free_pipe_info(struct pipe_inode_info *);
				 /* we may still block on the fd we splice */
				 /* from/to, of course */
#define SPLICE_F_MORE	(0x04)	/* expect more data */
#define SPLICE_F_GIFT	(0x08)	/* pages passed in are a gift */

/*
 * Passed to the actors