diff options
author | Kent Overstreet <kmo@daterainc.com> | 2014-03-31 16:48:12 -0700 |
---|---|---|
committer | Kent Overstreet <kmo@daterainc.com> | 2014-03-31 17:01:27 -0700 |
commit | 4d4ee754c4b9a1cfce24e9e6cc287b56bb55d591 (patch) | |
tree | e33f51256a81814f39feb84606421d359c48d445 | |
parent | 8260f75a638259a0ec21650897a9d7805954345d (diff) |
block: Add bio_get_user_pages()
This replaces some of the code that was in __bio_map_user_iov(), and
soon we're going to use this helper in the dio code.
Note that this relies on the recent change to make
generic_make_request() take arbitrary sized bios - we're not using
bio_add_page() here.
Signed-off-by: Kent Overstreet <kmo@daterainc.com>
Cc: Jens Axboe <axboe@kernel.dk>
Conflicts:
fs/bio.c
-rw-r--r-- | fs/bio.c | 65 | ||||
-rw-r--r-- | include/linux/bio.h | 2 |
2 files changed, 67 insertions, 0 deletions
@@ -1234,6 +1234,71 @@ struct bio *bio_copy_user(struct request_queue *q, struct rq_map_data *map_data, } EXPORT_SYMBOL(bio_copy_user); +/** + * bio_get_user_pages - pin user pages and add them to a biovec + * @bio: bio to add pages to + * @uaddr: start of user address + * @len: length in bytes + * @write_to_vm: bool indicating writing to pages or not + * + * Pins pages for up to @len bytes and appends them to @bio's bvec array. May + * pin only part of the requested pages - @bio need not have room for all the + * pages and can already have had pages added to it. + * + * Returns the number of bytes from @len added to @bio. + */ +ssize_t bio_get_user_pages(struct bio *bio, struct iov_iter *i, int write_to_vm) +{ + while (bio->bi_vcnt < bio->bi_max_vecs && iov_iter_count(i)) { + struct iovec iov = iov_iter_iovec(i); + int ret; + unsigned nr_pages, bytes; + unsigned offset = offset_in_page(iov.iov_base); + struct bio_vec *bv; + struct page **pages; + + nr_pages = min_t(size_t, + DIV_ROUND_UP(iov.iov_len + offset, PAGE_SIZE), + bio->bi_max_vecs - bio->bi_vcnt); + + bv = &bio->bi_io_vec[bio->bi_vcnt]; + pages = (void *) bv; + + ret = get_user_pages_fast((unsigned long) iov.iov_base, + nr_pages, write_to_vm, pages); + if (ret < 0) { + if (bio->bi_vcnt) + return 0; + + return ret; + } + + bio->bi_vcnt += ret; + bytes = ret * PAGE_SIZE - offset; + + while (ret--) { + bv[ret].bv_page = pages[ret]; + bv[ret].bv_len = PAGE_SIZE; + bv[ret].bv_offset = 0; + } + + bv[0].bv_offset += offset; + bv[0].bv_len -= offset; + + if (bytes > iov.iov_len) { + bio->bi_io_vec[bio->bi_vcnt - 1].bv_len -= + bytes - iov.iov_len; + bytes = iov.iov_len; + } + + bio->bi_iter.bi_size += bytes; + iov_iter_advance(i, bytes); + } + + return 0; +} +EXPORT_SYMBOL(bio_get_user_pages); + static struct bio *__bio_map_user_iov(struct request_queue *q, struct block_device *bdev, const struct iov_iter *iter, diff --git a/include/linux/bio.h b/include/linux/bio.h index 87c5cb3eab17..0074044a31c2 100644 --- a/include/linux/bio.h +++ b/include/linux/bio.h @@ -370,6 +370,8 @@ extern int bio_add_page(struct bio *, struct page *, unsigned int,unsigned int); extern int bio_add_pc_page(struct request_queue *, struct bio *, struct page *, unsigned int, unsigned int); extern int bio_get_nr_vecs(struct block_device *); +struct iov_iter; +extern ssize_t bio_get_user_pages(struct bio *, struct iov_iter *, int); extern struct bio *bio_map_user(struct request_queue *, struct block_device *, unsigned long, unsigned int, int, gfp_t); struct iov_iter; |