summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/block/ll_rw_blk.c56
-rw-r--r--fs/bio.c66
-rw-r--r--include/linux/bio.h2
-rw-r--r--include/linux/blkdev.h2
4 files changed, 126 insertions, 0 deletions
diff --git a/drivers/block/ll_rw_blk.c b/drivers/block/ll_rw_blk.c
index f20eba22b14b..e30a3c93b70c 100644
--- a/drivers/block/ll_rw_blk.c
+++ b/drivers/block/ll_rw_blk.c
@@ -281,6 +281,7 @@ static inline void rq_init(request_queue_t *q, struct request *rq)
rq->special = NULL;
rq->data_len = 0;
rq->data = NULL;
+ rq->nr_phys_segments = 0;
rq->sense = NULL;
rq->end_io = NULL;
rq->end_io_data = NULL;
@@ -2176,6 +2177,61 @@ int blk_rq_unmap_user(struct request *rq, struct bio *bio, unsigned int ulen)
EXPORT_SYMBOL(blk_rq_unmap_user);
+static int blk_rq_map_kern_endio(struct bio *bio, unsigned int bytes_done,
+ int error)
+{
+ if (bio->bi_size)
+ return 1;
+
+ bio_put(bio);
+ return 0;
+}
+
+/**
+ * blk_rq_map_kern - map kernel data to a request, for REQ_BLOCK_PC usage
+ * @q: request queue where request should be inserted
+ * @rw: READ or WRITE data
+ * @kbuf: the kernel buffer
+ * @len: length of user data
+ */
+struct request *blk_rq_map_kern(request_queue_t *q, int rw, void *kbuf,
+ unsigned int len, unsigned int gfp_mask)
+{
+ struct request *rq;
+ struct bio *bio;
+
+ if (len > (q->max_sectors << 9))
+ return ERR_PTR(-EINVAL);
+ if ((!len && kbuf) || (len && !kbuf))
+ return ERR_PTR(-EINVAL);
+
+ rq = blk_get_request(q, rw, gfp_mask);
+ if (!rq)
+ return ERR_PTR(-ENOMEM);
+
+ bio = bio_map_kern(q, kbuf, len, gfp_mask);
+ if (!IS_ERR(bio)) {
+ if (rw)
+ bio->bi_rw |= (1 << BIO_RW);
+ bio->bi_end_io = blk_rq_map_kern_endio;
+
+ rq->bio = rq->biotail = bio;
+ blk_rq_bio_prep(q, rq, bio);
+
+ rq->buffer = rq->data = NULL;
+ rq->data_len = len;
+ return rq;
+ }
+
+ /*
+ * bio is the err-ptr
+ */
+ blk_put_request(rq);
+ return (struct request *) bio;
+}
+
+EXPORT_SYMBOL(blk_rq_map_kern);
+
/**
* blk_execute_rq - insert a request into queue for execution
* @q: queue to insert the request in
diff --git a/fs/bio.c b/fs/bio.c
index 3a1472acc361..707b9af2dd01 100644
--- a/fs/bio.c
+++ b/fs/bio.c
@@ -701,6 +701,71 @@ void bio_unmap_user(struct bio *bio)
bio_put(bio);
}
+static struct bio *__bio_map_kern(request_queue_t *q, void *data,
+ unsigned int len, unsigned int gfp_mask)
+{
+ unsigned long kaddr = (unsigned long)data;
+ unsigned long end = (kaddr + len + PAGE_SIZE - 1) >> PAGE_SHIFT;
+ unsigned long start = kaddr >> PAGE_SHIFT;
+ const int nr_pages = end - start;
+ int offset, i;
+ struct bio *bio;
+
+ bio = bio_alloc(gfp_mask, nr_pages);
+ if (!bio)
+ return ERR_PTR(-ENOMEM);
+
+ offset = offset_in_page(kaddr);
+ for (i = 0; i < nr_pages; i++) {
+ unsigned int bytes = PAGE_SIZE - offset;
+
+ if (len <= 0)
+ break;
+
+ if (bytes > len)
+ bytes = len;
+
+ if (__bio_add_page(q, bio, virt_to_page(data), bytes,
+ offset) < bytes)
+ break;
+
+ data += bytes;
+ len -= bytes;
+ offset = 0;
+ }
+
+ return bio;
+}
+
+/**
+ * bio_map_kern - map kernel address into bio
+ * @q: the request_queue_t for the bio
+ * @data: pointer to buffer to map
+ * @len: length in bytes
+ * @gfp_mask: allocation flags for bio allocation
+ *
+ * Map the kernel address into a bio suitable for io to a block
+ * device. Returns an error pointer in case of error.
+ */
+struct bio *bio_map_kern(request_queue_t *q, void *data, unsigned int len,
+ unsigned int gfp_mask)
+{
+ struct bio *bio;
+
+ bio = __bio_map_kern(q, data, len, gfp_mask);
+ if (IS_ERR(bio))
+ return bio;
+
+ if (bio->bi_size == len)
+ return bio;
+
+ /*
+ * Don't support partial mappings.
+ */
+ bio_put(bio);
+ return ERR_PTR(-EINVAL);
+}
+
/*
* bio_set_pages_dirty() and bio_check_pages_dirty() are support functions
* for performing direct-IO in BIOs.
@@ -1088,6 +1153,7 @@ EXPORT_SYMBOL(bio_add_page);
EXPORT_SYMBOL(bio_get_nr_vecs);
EXPORT_SYMBOL(bio_map_user);
EXPORT_SYMBOL(bio_unmap_user);
+EXPORT_SYMBOL(bio_map_kern);
EXPORT_SYMBOL(bio_pair_release);
EXPORT_SYMBOL(bio_split);
EXPORT_SYMBOL(bio_split_pool);
diff --git a/include/linux/bio.h b/include/linux/bio.h
index 038022763f09..1dd2bc2e84ae 100644
--- a/include/linux/bio.h
+++ b/include/linux/bio.h
@@ -282,6 +282,8 @@ extern int bio_get_nr_vecs(struct block_device *);
extern struct bio *bio_map_user(struct request_queue *, struct block_device *,
unsigned long, unsigned int, int);
extern void bio_unmap_user(struct bio *);
+extern struct bio *bio_map_kern(struct request_queue *, void *, unsigned int,
+ unsigned int);
extern void bio_set_pages_dirty(struct bio *bio);
extern void bio_check_pages_dirty(struct bio *bio);
extern struct bio *bio_copy_user(struct request_queue *, unsigned long, unsigned int, int);
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 4a99b76c5a33..67339bc5f6bc 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -560,6 +560,8 @@ extern void blk_run_queue(request_queue_t *);
extern void blk_queue_activity_fn(request_queue_t *, activity_fn *, void *);
extern struct request *blk_rq_map_user(request_queue_t *, int, void __user *, unsigned int);
extern int blk_rq_unmap_user(struct request *, struct bio *, unsigned int);
+extern struct request *blk_rq_map_kern(request_queue_t *, int, void *,
+ unsigned int, unsigned int);
extern int blk_execute_rq(request_queue_t *, struct gendisk *, struct request *);
static inline request_queue_t *bdev_get_queue(struct block_device *bdev)