]> git.proxmox.com Git - mirror_ubuntu-eoan-kernel.git/blobdiff - block/blk-merge.c
block: avoid to READ fields of null bio
[mirror_ubuntu-eoan-kernel.git] / block / blk-merge.c
index 71e9ac03f62187a37abf5eae88f0008a4c18e5a4..066b66430523d64f9eee7888e24c1f65084b0497 100644 (file)
@@ -161,6 +161,73 @@ static inline unsigned get_max_io_size(struct request_queue *q,
        return sectors;
 }
 
+static unsigned get_max_segment_size(struct request_queue *q,
+                                    unsigned offset)
+{
+       unsigned long mask = queue_segment_boundary(q);
+
+       /* default segment boundary mask means no boundary limit */
+       if (mask == BLK_SEG_BOUNDARY_MASK)
+               return queue_max_segment_size(q);
+
+       return min_t(unsigned long, mask - (mask & offset) + 1,
+                    queue_max_segment_size(q));
+}
+
+/*
+ * Split the bvec @bv into segments, and update all kinds of
+ * variables.
+ */
+static bool bvec_split_segs(struct request_queue *q, struct bio_vec *bv,
+               unsigned *nsegs, unsigned *last_seg_size,
+               unsigned *front_seg_size, unsigned *sectors)
+{
+       unsigned len = bv->bv_len;
+       unsigned total_len = 0;
+       unsigned new_nsegs = 0, seg_size = 0;
+
+       /*
+        * Multi-page bvec may be too big to hold in one segment, so the
+        * current bvec has to be splitted as multiple segments.
+        */
+       while (len && new_nsegs + *nsegs < queue_max_segments(q)) {
+               seg_size = get_max_segment_size(q, bv->bv_offset + total_len);
+               seg_size = min(seg_size, len);
+
+               new_nsegs++;
+               total_len += seg_size;
+               len -= seg_size;
+
+               if ((bv->bv_offset + total_len) & queue_virt_boundary(q))
+                       break;
+       }
+
+       if (!new_nsegs)
+               return !!len;
+
+       /* update front segment size */
+       if (!*nsegs) {
+               unsigned first_seg_size;
+
+               if (new_nsegs == 1)
+                       first_seg_size = get_max_segment_size(q, bv->bv_offset);
+               else
+                       first_seg_size = queue_max_segment_size(q);
+
+               if (*front_seg_size < first_seg_size)
+                       *front_seg_size = first_seg_size;
+       }
+
+       /* update other varibles */
+       *last_seg_size = seg_size;
+       *nsegs += new_nsegs;
+       if (sectors)
+               *sectors += total_len >> 9;
+
+       /* split in the middle of the bvec if len != 0 */
+       return !!len;
+}
+
 static struct bio *blk_bio_segment_split(struct request_queue *q,
                                         struct bio *bio,
                                         struct bio_set *bs,
@@ -174,7 +241,7 @@ static struct bio *blk_bio_segment_split(struct request_queue *q,
        struct bio *new = NULL;
        const unsigned max_sectors = get_max_io_size(q, bio);
 
-       bio_for_each_segment(bv, bio, iter) {
+       bio_for_each_bvec(bv, bio, iter) {
                /*
                 * If the queue doesn't support SG gaps and adding this
                 * offset would create a gap, disallow it.
@@ -189,8 +256,12 @@ static struct bio *blk_bio_segment_split(struct request_queue *q,
                         */
                        if (nsegs < queue_max_segments(q) &&
                            sectors < max_sectors) {
-                               nsegs++;
-                               sectors = max_sectors;
+                               /* split in the middle of bvec */
+                               bv.bv_len = (max_sectors - sectors) << 9;
+                               bvec_split_segs(q, &bv, &nsegs,
+                                               &seg_size,
+                                               &front_seg_size,
+                                               &sectors);
                        }
                        goto split;
                }
@@ -212,14 +283,12 @@ new_segment:
                if (nsegs == queue_max_segments(q))
                        goto split;
 
-               if (nsegs == 1 && seg_size > front_seg_size)
-                       front_seg_size = seg_size;
-
-               nsegs++;
                bvprv = bv;
                bvprvp = &bvprv;
-               seg_size = bv.bv_len;
-               sectors += bv.bv_len >> 9;
+
+               if (bvec_split_segs(q, &bv, &nsegs, &seg_size,
+                                   &front_seg_size, &sectors))
+                       goto split;
 
        }
 
@@ -233,8 +302,6 @@ split:
                        bio = new;
        }
 
-       if (nsegs == 1 && seg_size > front_seg_size)
-               front_seg_size = seg_size;
        bio->bi_seg_front_size = front_seg_size;
        if (seg_size > bio->bi_seg_back_size)
                bio->bi_seg_back_size = seg_size;
@@ -291,18 +358,20 @@ void blk_queue_split(struct request_queue *q, struct bio **bio)
 EXPORT_SYMBOL(blk_queue_split);
 
 static unsigned int __blk_recalc_rq_segments(struct request_queue *q,
-                                            struct bio *bio,
-                                            bool no_sg_merge)
+                                            struct bio *bio)
 {
        struct bio_vec bv, bvprv = { NULL };
        int prev = 0;
        unsigned int seg_size, nr_phys_segs;
+       unsigned front_seg_size;
        struct bio *fbio, *bbio;
        struct bvec_iter iter;
 
        if (!bio)
                return 0;
 
+       front_seg_size = bio->bi_seg_front_size;
+
        switch (bio_op(bio)) {
        case REQ_OP_DISCARD:
        case REQ_OP_SECURE_ERASE:
@@ -316,14 +385,7 @@ static unsigned int __blk_recalc_rq_segments(struct request_queue *q,
        seg_size = 0;
        nr_phys_segs = 0;
        for_each_bio(bio) {
-               bio_for_each_segment(bv, bio, iter) {
-                       /*
-                        * If SG merging is disabled, each bio vector is
-                        * a segment
-                        */
-                       if (no_sg_merge)
-                               goto new_segment;
-
+               bio_for_each_bvec(bv, bio, iter) {
                        if (prev) {
                                if (seg_size + bv.bv_len
                                    > queue_max_segment_size(q))
@@ -336,20 +398,15 @@ static unsigned int __blk_recalc_rq_segments(struct request_queue *q,
                                continue;
                        }
 new_segment:
-                       if (nr_phys_segs == 1 && seg_size >
-                           fbio->bi_seg_front_size)
-                               fbio->bi_seg_front_size = seg_size;
-
-                       nr_phys_segs++;
                        bvprv = bv;
                        prev = 1;
-                       seg_size = bv.bv_len;
+                       bvec_split_segs(q, &bv, &nr_phys_segs, &seg_size,
+                                       &front_seg_size, NULL);
                }
                bbio = bio;
        }
 
-       if (nr_phys_segs == 1 && seg_size > fbio->bi_seg_front_size)
-               fbio->bi_seg_front_size = seg_size;
+       fbio->bi_seg_front_size = front_seg_size;
        if (seg_size > bbio->bi_seg_back_size)
                bbio->bi_seg_back_size = seg_size;
 
@@ -358,33 +415,16 @@ new_segment:
 
 void blk_recalc_rq_segments(struct request *rq)
 {
-       bool no_sg_merge = !!test_bit(QUEUE_FLAG_NO_SG_MERGE,
-                       &rq->q->queue_flags);
-
-       rq->nr_phys_segments = __blk_recalc_rq_segments(rq->q, rq->bio,
-                       no_sg_merge);
+       rq->nr_phys_segments = __blk_recalc_rq_segments(rq->q, rq->bio);
 }
 
 void blk_recount_segments(struct request_queue *q, struct bio *bio)
 {
-       unsigned short seg_cnt;
-
-       /* estimate segment number by bi_vcnt for non-cloned bio */
-       if (bio_flagged(bio, BIO_CLONED))
-               seg_cnt = bio_segments(bio);
-       else
-               seg_cnt = bio->bi_vcnt;
-
-       if (test_bit(QUEUE_FLAG_NO_SG_MERGE, &q->queue_flags) &&
-                       (seg_cnt < queue_max_segments(q)))
-               bio->bi_phys_segments = seg_cnt;
-       else {
-               struct bio *nxt = bio->bi_next;
+       struct bio *nxt = bio->bi_next;
 
-               bio->bi_next = NULL;
-               bio->bi_phys_segments = __blk_recalc_rq_segments(q, bio, false);
-               bio->bi_next = nxt;
-       }
+       bio->bi_next = NULL;
+       bio->bi_phys_segments = __blk_recalc_rq_segments(q, bio);
+       bio->bi_next = nxt;
 
        bio_set_flag(bio, BIO_SEG_VALID);
 }
@@ -407,6 +447,54 @@ static int blk_phys_contig_segment(struct request_queue *q, struct bio *bio,
        return biovec_phys_mergeable(q, &end_bv, &nxt_bv);
 }
 
+static struct scatterlist *blk_next_sg(struct scatterlist **sg,
+               struct scatterlist *sglist)
+{
+       if (!*sg)
+               return sglist;
+
+       /*
+        * If the driver previously mapped a shorter list, we could see a
+        * termination bit prematurely unless it fully inits the sg table
+        * on each mapping. We KNOW that there must be more entries here
+        * or the driver would be buggy, so force clear the termination bit
+        * to avoid doing a full sg_init_table() in drivers for each command.
+        */
+       sg_unmark_end(*sg);
+       return sg_next(*sg);
+}
+
+static unsigned blk_bvec_map_sg(struct request_queue *q,
+               struct bio_vec *bvec, struct scatterlist *sglist,
+               struct scatterlist **sg)
+{
+       unsigned nbytes = bvec->bv_len;
+       unsigned nsegs = 0, total = 0, offset = 0;
+
+       while (nbytes > 0) {
+               unsigned seg_size;
+               struct page *pg;
+               unsigned idx;
+
+               *sg = blk_next_sg(sg, sglist);
+
+               seg_size = get_max_segment_size(q, bvec->bv_offset + total);
+               seg_size = min(nbytes, seg_size);
+
+               offset = (total + bvec->bv_offset) % PAGE_SIZE;
+               idx = (total + bvec->bv_offset) / PAGE_SIZE;
+               pg = nth_page(bvec->bv_page, idx);
+
+               sg_set_page(*sg, pg, seg_size, offset);
+
+               total += seg_size;
+               nbytes -= seg_size;
+               nsegs++;
+       }
+
+       return nsegs;
+}
+
 static inline void
 __blk_segment_map_sg(struct request_queue *q, struct bio_vec *bvec,
                     struct scatterlist *sglist, struct bio_vec *bvprv,
@@ -424,25 +512,7 @@ __blk_segment_map_sg(struct request_queue *q, struct bio_vec *bvec,
                (*sg)->length += nbytes;
        } else {
 new_segment:
-               if (!*sg)
-                       *sg = sglist;
-               else {
-                       /*
-                        * If the driver previously mapped a shorter
-                        * list, we could see a termination bit
-                        * prematurely unless it fully inits the sg
-                        * table on each mapping. We KNOW that there
-                        * must be more entries here or the driver
-                        * would be buggy, so force clear the
-                        * termination bit to avoid doing a full
-                        * sg_init_table() in drivers for each command.
-                        */
-                       sg_unmark_end(*sg);
-                       *sg = sg_next(*sg);
-               }
-
-               sg_set_page(*sg, bvec->bv_page, nbytes, bvec->bv_offset);
-               (*nsegs)++;
+               (*nsegs) += blk_bvec_map_sg(q, bvec, sglist, sg);
        }
        *bvprv = *bvec;
 }
@@ -464,7 +534,7 @@ static int __blk_bios_map_sg(struct request_queue *q, struct bio *bio,
        int nsegs = 0;
 
        for_each_bio(bio)
-               bio_for_each_segment(bvec, bio, iter)
+               bio_for_each_bvec(bvec, bio, iter)
                        __blk_segment_map_sg(q, &bvec, sglist, &bvprv, sg,
                                             &nsegs);