]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blobdiff - block/bio.c
block: move guard_bio_eod to bio.c
[mirror_ubuntu-jammy-kernel.git] / block / bio.c
index a5d75f6bf4c7eedc96454bade72dbc9fe88af74d..11e6aac35092db56d0e31f4967e8fe708b5bc9b4 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/cgroup.h>
 #include <linux/blk-cgroup.h>
 #include <linux/highmem.h>
+#include <linux/sched/sysctl.h>
 
 #include <trace/events/block.h>
 #include "blk.h"
@@ -538,6 +539,98 @@ void zero_fill_bio_iter(struct bio *bio, struct bvec_iter start)
 }
 EXPORT_SYMBOL(zero_fill_bio_iter);
 
+/**
+ * bio_truncate - truncate the bio to small size of @new_size
+ * @bio:       the bio to be truncated
+ * @new_size:  new size for truncating the bio
+ *
+ * Description:
+ *   Truncate the bio to new size of @new_size. If bio_op(bio) is
+ *   REQ_OP_READ, zero the truncated part. This function should only
+ *   be used for handling corner cases, such as bio eod.
+ */
+void bio_truncate(struct bio *bio, unsigned new_size)
+{
+       struct bio_vec bv;
+       struct bvec_iter iter;
+       unsigned int done = 0;
+       bool truncated = false;
+
+       if (new_size >= bio->bi_iter.bi_size)
+               return;
+
+       if (bio_op(bio) != REQ_OP_READ)
+               goto exit;
+
+       bio_for_each_segment(bv, bio, iter) {
+               if (done + bv.bv_len > new_size) {
+                       unsigned offset;
+
+                       if (!truncated)
+                               offset = new_size - done;
+                       else
+                               offset = 0;
+                       zero_user(bv.bv_page, offset, bv.bv_len - offset);
+                       truncated = true;
+               }
+               done += bv.bv_len;
+       }
+
+ exit:
+       /*
+        * Don't touch bvec table here and make it really immutable, since
+        * fs bio user has to retrieve all pages via bio_for_each_segment_all
+        * in its .end_bio() callback.
+        *
+        * It is enough to truncate bio by updating .bi_size since we can make
+        * correct bvec with the updated .bi_size for drivers.
+        */
+       bio->bi_iter.bi_size = new_size;
+}
+
+/**
+ * guard_bio_eod - truncate a BIO to fit the block device
+ * @bio:       bio to truncate
+ *
+ * This allows us to do IO even on the odd last sectors of a device, even if the
+ * block size is some multiple of the physical sector size.
+ *
+ * We'll just truncate the bio to the size of the device, and clear the end of
+ * the buffer head manually.  Truly out-of-range accesses will turn into actual
+ * I/O errors, this only handles the "we need to be able to do I/O at the final
+ * sector" case.
+ */
+void guard_bio_eod(struct bio *bio)
+{
+       sector_t maxsector;
+       struct hd_struct *part;
+
+       rcu_read_lock();
+       part = __disk_get_part(bio->bi_disk, bio->bi_partno);
+       if (part)
+               maxsector = part_nr_sects_read(part);
+       else
+               maxsector = get_capacity(bio->bi_disk);
+       rcu_read_unlock();
+
+       if (!maxsector)
+               return;
+
+       /*
+        * If the *whole* IO is past the end of the device,
+        * let it through, and the IO layer will turn it into
+        * an EIO.
+        */
+       if (unlikely(bio->bi_iter.bi_sector >= maxsector))
+               return;
+
+       maxsector -= bio->bi_iter.bi_sector;
+       if (likely((bio->bi_iter.bi_size >> 9) <= maxsector))
+               return;
+
+       bio_truncate(bio, maxsector << 9);
+}
+
 /**
  * bio_put - release a reference to a bio
  * @bio:   bio to release reference to
@@ -630,6 +723,12 @@ struct bio *bio_clone_fast(struct bio *bio, gfp_t gfp_mask, struct bio_set *bs)
 }
 EXPORT_SYMBOL(bio_clone_fast);
 
+const char *bio_devname(struct bio *bio, char *buf)
+{
+       return disk_name(bio->bi_disk, bio->bi_partno, buf);
+}
+EXPORT_SYMBOL(bio_devname);
+
 static inline bool page_is_mergeable(const struct bio_vec *bv,
                struct page *page, unsigned int len, unsigned int off,
                bool *same_page)
@@ -970,12 +1069,21 @@ static void submit_bio_wait_endio(struct bio *bio)
 int submit_bio_wait(struct bio *bio)
 {
        DECLARE_COMPLETION_ONSTACK_MAP(done, bio->bi_disk->lockdep_map);
+       unsigned long hang_check;
 
        bio->bi_private = &done;
        bio->bi_end_io = submit_bio_wait_endio;
        bio->bi_opf |= REQ_SYNC;
        submit_bio(bio);
-       wait_for_completion_io(&done);
+
+       /* Prevent hang_check timer from firing at us during very long I/O */
+       hang_check = sysctl_hung_task_timeout_secs;
+       if (hang_check)
+               while (!wait_for_completion_io_timeout(&done,
+                                       hang_check * (HZ/2)))
+                       ;
+       else
+               wait_for_completion_io(&done);
 
        return blk_status_to_errno(bio->bi_status);
 }
@@ -1703,14 +1811,14 @@ defer:
        schedule_work(&bio_dirty_work);
 }
 
-void update_io_ticks(struct hd_struct *part, unsigned long now)
+void update_io_ticks(struct hd_struct *part, unsigned long now, bool end)
 {
        unsigned long stamp;
 again:
        stamp = READ_ONCE(part->stamp);
        if (unlikely(stamp != now)) {
                if (likely(cmpxchg(&part->stamp, stamp, now) == stamp)) {
-                       __part_stat_add(part, io_ticks, 1);
+                       __part_stat_add(part, io_ticks, end ? now - stamp : 1);
                }
        }
        if (part->partno) {
@@ -1726,7 +1834,7 @@ void generic_start_io_acct(struct request_queue *q, int op,
 
        part_stat_lock();
 
-       update_io_ticks(part, jiffies);
+       update_io_ticks(part, jiffies, false);
        part_stat_inc(part, ios[sgrp]);
        part_stat_add(part, sectors[sgrp], sectors);
        part_inc_in_flight(q, part, op_is_write(op));
@@ -1744,9 +1852,8 @@ void generic_end_io_acct(struct request_queue *q, int req_op,
 
        part_stat_lock();
 
-       update_io_ticks(part, now);
+       update_io_ticks(part, now, true);
        part_stat_add(part, nsecs[sgrp], jiffies_to_nsecs(duration));
-       part_stat_add(part, time_in_queue, duration);
        part_dec_in_flight(q, part, op_is_write(req_op));
 
        part_stat_unlock();