Merge tag 'pull-bd_flags-2' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
authorLinus Torvalds <torvalds@linux-foundation.org>
Tue, 21 May 2024 20:02:56 +0000 (13:02 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 21 May 2024 20:02:56 +0000 (13:02 -0700)
Pull bdev flags update from Al Viro:
 "Compactifying bdev flags.

  We can easily have up to 24 flags with sane atomicity, _without_
  pushing anything out of the first cacheline of struct block_device"

* tag 'pull-bd_flags-2' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  bdev: move ->bd_make_it_fail to ->__bd_flags
  bdev: move ->bd_ro_warned to ->__bd_flags
  bdev: move ->bd_has_subit_bio to ->__bd_flags
  bdev: move ->bd_write_holder into ->__bd_flags
  bdev: move ->bd_read_only to ->__bd_flags
  bdev: infrastructure for flags
  wrapper for access to ->bd_partno
  Use bdev_is_paritition() instead of open-coding it

1  2 
block/bdev.c
block/blk-core.c
block/blk-mq.c
block/blk-zoned.c
block/genhd.c
block/ioctl.c
block/partitions/core.c
include/linux/blk_types.h
include/linux/blkdev.h

diff --cc block/bdev.c
index 5a03c93ba2221b83c663ae74cb83a81ca9db9ceb,fae30eae7741a15ee39cd88e8ae5988cb0b18740..353677ac49b3b9677f918340c622918617cee3fd
@@@ -422,13 -411,11 +422,11 @@@ struct block_device *bdev_alloc(struct 
        mutex_init(&bdev->bd_fsfreeze_mutex);
        spin_lock_init(&bdev->bd_size_lock);
        mutex_init(&bdev->bd_holder_lock);
-       bdev->bd_partno = partno;
+       atomic_set(&bdev->__bd_flags, partno);
 -      bdev->bd_inode = inode;
 +      bdev->bd_mapping = &inode->i_data;
        bdev->bd_queue = disk->queue;
-       if (partno)
-               bdev->bd_has_submit_bio = disk->part0->bd_has_submit_bio;
-       else
-               bdev->bd_has_submit_bio = false;
+       if (partno && bdev_test_flag(disk->part0, BD_HAS_SUBMIT_BIO))
+               bdev_set_flag(bdev, BD_HAS_SUBMIT_BIO);
        bdev->bd_stats = alloc_percpu(struct disk_stats);
        if (!bdev->bd_stats) {
                iput(inode);
index 01186333c88ec5bbcc5c1bb9cbe2bbe723f84a91,1076336dd62076fb40e81fc8c8583eb762ef9f5e..ea44b13af9afee5c5a38c0800814adaee7cca681
@@@ -986,12 -988,11 +989,12 @@@ void update_io_ticks(struct block_devic
        unsigned long stamp;
  again:
        stamp = READ_ONCE(part->bd_stamp);
 -      if (unlikely(time_after(now, stamp))) {
 -              if (likely(try_cmpxchg(&part->bd_stamp, &stamp, now)))
 -                      __part_stat_add(part, io_ticks, end ? now - stamp : 1);
 -      }
 +      if (unlikely(time_after(now, stamp)) &&
 +          likely(try_cmpxchg(&part->bd_stamp, &stamp, now)) &&
 +          (end || part_in_flight(part)))
 +              __part_stat_add(part, io_ticks, now - stamp);
 +
-       if (part->bd_partno) {
+       if (bdev_is_partition(part)) {
                part = bdev_whole(part);
                goto again;
        }
diff --cc block/blk-mq.c
Simple merge
index 57d367ada1f237774daf945aadc4b92050d04178,da0f4b2a8fa09330bdc71bf545ed2dc688326392..03aa4eead39e7bdbf2b66756382eebf9f7d458d2
        return ret;
  }
  
 -void disk_free_zone_bitmaps(struct gendisk *disk)
 +static inline bool disk_zone_is_conv(struct gendisk *disk, sector_t sector)
  {
 -      kfree(disk->conv_zones_bitmap);
 -      disk->conv_zones_bitmap = NULL;
 -      kfree(disk->seq_zones_wlock);
 -      disk->seq_zones_wlock = NULL;
 +      if (!disk->conv_zones_bitmap)
 +              return false;
 +      return test_bit(disk_zone_no(disk, sector), disk->conv_zones_bitmap);
  }
  
 -struct blk_revalidate_zone_args {
 -      struct gendisk  *disk;
 -      unsigned long   *conv_zones_bitmap;
 -      unsigned long   *seq_zones_wlock;
 -      unsigned int    nr_zones;
 -      sector_t        sector;
 -};
 +static bool disk_insert_zone_wplug(struct gendisk *disk,
 +                                 struct blk_zone_wplug *zwplug)
 +{
 +      struct blk_zone_wplug *zwplg;
 +      unsigned long flags;
 +      unsigned int idx =
 +              hash_32(zwplug->zone_no, disk->zone_wplugs_hash_bits);
  
 -/*
 - * Helper function to check the validity of zones of a zoned block device.
 - */
 -static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,
 -                                void *data)
 +      /*
 +       * Add the new zone write plug to the hash table, but carefully as we
 +       * are racing with other submission context, so we may already have a
 +       * zone write plug for the same zone.
 +       */
 +      spin_lock_irqsave(&disk->zone_wplugs_lock, flags);
 +      hlist_for_each_entry_rcu(zwplg, &disk->zone_wplugs_hash[idx], node) {
 +              if (zwplg->zone_no == zwplug->zone_no) {
 +                      spin_unlock_irqrestore(&disk->zone_wplugs_lock, flags);
 +                      return false;
 +              }
 +      }
 +      hlist_add_head_rcu(&zwplug->node, &disk->zone_wplugs_hash[idx]);
 +      spin_unlock_irqrestore(&disk->zone_wplugs_lock, flags);
 +
 +      return true;
 +}
 +
 +static struct blk_zone_wplug *disk_get_zone_wplug(struct gendisk *disk,
 +                                                sector_t sector)
  {
 -      struct blk_revalidate_zone_args *args = data;
 -      struct gendisk *disk = args->disk;
 -      struct request_queue *q = disk->queue;
 -      sector_t capacity = get_capacity(disk);
 -      sector_t zone_sectors = q->limits.chunk_sectors;
 +      unsigned int zno = disk_zone_no(disk, sector);
 +      unsigned int idx = hash_32(zno, disk->zone_wplugs_hash_bits);
 +      struct blk_zone_wplug *zwplug;
  
 -      /* Check for bad zones and holes in the zone report */
 -      if (zone->start != args->sector) {
 -              pr_warn("%s: Zone gap at sectors %llu..%llu\n",
 -                      disk->disk_name, args->sector, zone->start);
 -              return -ENODEV;
 +      rcu_read_lock();
 +
 +      hlist_for_each_entry_rcu(zwplug, &disk->zone_wplugs_hash[idx], node) {
 +              if (zwplug->zone_no == zno &&
 +                  atomic_inc_not_zero(&zwplug->ref)) {
 +                      rcu_read_unlock();
 +                      return zwplug;
 +              }
        }
  
 -      if (zone->start >= capacity || !zone->len) {
 -              pr_warn("%s: Invalid zone start %llu, length %llu\n",
 -                      disk->disk_name, zone->start, zone->len);
 -              return -ENODEV;
 +      rcu_read_unlock();
 +
 +      return NULL;
 +}
 +
 +static void disk_free_zone_wplug_rcu(struct rcu_head *rcu_head)
 +{
 +      struct blk_zone_wplug *zwplug =
 +              container_of(rcu_head, struct blk_zone_wplug, rcu_head);
 +
 +      mempool_free(zwplug, zwplug->disk->zone_wplugs_pool);
 +}
 +
 +static inline void disk_put_zone_wplug(struct blk_zone_wplug *zwplug)
 +{
 +      if (atomic_dec_and_test(&zwplug->ref)) {
 +              WARN_ON_ONCE(!bio_list_empty(&zwplug->bio_list));
 +              WARN_ON_ONCE(!list_empty(&zwplug->link));
 +              WARN_ON_ONCE(!(zwplug->flags & BLK_ZONE_WPLUG_UNHASHED));
 +
 +              call_rcu(&zwplug->rcu_head, disk_free_zone_wplug_rcu);
        }
 +}
 +
 +static inline bool disk_should_remove_zone_wplug(struct gendisk *disk,
 +                                               struct blk_zone_wplug *zwplug)
 +{
 +      /* If the zone write plug was already removed, we are done. */
 +      if (zwplug->flags & BLK_ZONE_WPLUG_UNHASHED)
 +              return false;
 +
 +      /* If the zone write plug is still busy, it cannot be removed. */
 +      if (zwplug->flags & BLK_ZONE_WPLUG_BUSY)
 +              return false;
  
        /*
 -       * All zones must have the same size, with the exception on an eventual
 -       * smaller last zone.
 +       * Completions of BIOs with blk_zone_write_plug_bio_endio() may
 +       * happen after handling a request completion with
 +       * blk_zone_write_plug_finish_request() (e.g. with split BIOs
 +       * that are chained). In such case, disk_zone_wplug_unplug_bio()
 +       * should not attempt to remove the zone write plug until all BIO
 +       * completions are seen. Check by looking at the zone write plug
 +       * reference count, which is 2 when the plug is unused (one reference
 +       * taken when the plug was allocated and another reference taken by the
 +       * caller context).
         */
 -      if (zone->start + zone->len < capacity) {
 -              if (zone->len != zone_sectors) {
 -                      pr_warn("%s: Invalid zoned device with non constant zone size\n",
 -                              disk->disk_name);
 -                      return -ENODEV;
 +      if (atomic_read(&zwplug->ref) > 2)
 +              return false;
 +
 +      /* We can remove zone write plugs for zones that are empty or full. */
 +      return !zwplug->wp_offset || zwplug->wp_offset >= disk->zone_capacity;
 +}
 +
 +static void disk_remove_zone_wplug(struct gendisk *disk,
 +                                 struct blk_zone_wplug *zwplug)
 +{
 +      unsigned long flags;
 +
 +      /* If the zone write plug was already removed, we have nothing to do. */
 +      if (zwplug->flags & BLK_ZONE_WPLUG_UNHASHED)
 +              return;
 +
 +      /*
 +       * Mark the zone write plug as unhashed and drop the extra reference we
 +       * took when the plug was inserted in the hash table.
 +       */
 +      zwplug->flags |= BLK_ZONE_WPLUG_UNHASHED;
 +      spin_lock_irqsave(&disk->zone_wplugs_lock, flags);
 +      hlist_del_init_rcu(&zwplug->node);
 +      spin_unlock_irqrestore(&disk->zone_wplugs_lock, flags);
 +      disk_put_zone_wplug(zwplug);
 +}
 +
 +static void blk_zone_wplug_bio_work(struct work_struct *work);
 +
 +/*
 + * Get a reference on the write plug for the zone containing @sector.
 + * If the plug does not exist, it is allocated and hashed.
 + * Return a pointer to the zone write plug with the plug spinlock held.
 + */
 +static struct blk_zone_wplug *disk_get_and_lock_zone_wplug(struct gendisk *disk,
 +                                      sector_t sector, gfp_t gfp_mask,
 +                                      unsigned long *flags)
 +{
 +      unsigned int zno = disk_zone_no(disk, sector);
 +      struct blk_zone_wplug *zwplug;
 +
 +again:
 +      zwplug = disk_get_zone_wplug(disk, sector);
 +      if (zwplug) {
 +              /*
 +               * Check that a BIO completion or a zone reset or finish
 +               * operation has not already removed the zone write plug from
 +               * the hash table and dropped its reference count. In such case,
 +               * we need to get a new plug so start over from the beginning.
 +               */
 +              spin_lock_irqsave(&zwplug->lock, *flags);
 +              if (zwplug->flags & BLK_ZONE_WPLUG_UNHASHED) {
 +                      spin_unlock_irqrestore(&zwplug->lock, *flags);
 +                      disk_put_zone_wplug(zwplug);
 +                      goto again;
                }
 -      } else if (zone->len > zone_sectors) {
 -              pr_warn("%s: Invalid zoned device with larger last zone size\n",
 -                      disk->disk_name);
 -              return -ENODEV;
 +              return zwplug;
        }
  
 -      /* Check zone type */
 -      switch (zone->type) {
 -      case BLK_ZONE_TYPE_CONVENTIONAL:
 -              if (!args->conv_zones_bitmap) {
 -                      args->conv_zones_bitmap =
 -                              blk_alloc_zone_bitmap(q->node, args->nr_zones);
 -                      if (!args->conv_zones_bitmap)
 -                              return -ENOMEM;
 -              }
 -              set_bit(idx, args->conv_zones_bitmap);
 -              break;
 -      case BLK_ZONE_TYPE_SEQWRITE_REQ:
 -              if (!args->seq_zones_wlock) {
 -                      args->seq_zones_wlock =
 -                              blk_alloc_zone_bitmap(q->node, args->nr_zones);
 -                      if (!args->seq_zones_wlock)
 -                              return -ENOMEM;
 -              }
 -              break;
 -      case BLK_ZONE_TYPE_SEQWRITE_PREF:
 -      default:
 -              pr_warn("%s: Invalid zone type 0x%x at sectors %llu\n",
 -                      disk->disk_name, (int)zone->type, zone->start);
 -              return -ENODEV;
 +      /*
 +       * Allocate and initialize a zone write plug with an extra reference
 +       * so that it is not freed when the zone write plug becomes idle without
 +       * the zone being full.
 +       */
 +      zwplug = mempool_alloc(disk->zone_wplugs_pool, gfp_mask);
 +      if (!zwplug)
 +              return NULL;
 +
 +      INIT_HLIST_NODE(&zwplug->node);
 +      INIT_LIST_HEAD(&zwplug->link);
 +      atomic_set(&zwplug->ref, 2);
 +      spin_lock_init(&zwplug->lock);
 +      zwplug->flags = 0;
 +      zwplug->zone_no = zno;
 +      zwplug->wp_offset = sector & (disk->queue->limits.chunk_sectors - 1);
 +      bio_list_init(&zwplug->bio_list);
 +      INIT_WORK(&zwplug->bio_work, blk_zone_wplug_bio_work);
 +      zwplug->disk = disk;
 +
 +      spin_lock_irqsave(&zwplug->lock, *flags);
 +
 +      /*
 +       * Insert the new zone write plug in the hash table. This can fail only
 +       * if another context already inserted a plug. Retry from the beginning
 +       * in such case.
 +       */
 +      if (!disk_insert_zone_wplug(disk, zwplug)) {
 +              spin_unlock_irqrestore(&zwplug->lock, *flags);
 +              mempool_free(zwplug, disk->zone_wplugs_pool);
 +              goto again;
        }
  
 -      args->sector += zone->len;
 -      return 0;
 +      return zwplug;
  }
  
 -/**
 - * blk_revalidate_disk_zones - (re)allocate and initialize zone bitmaps
 - * @disk:     Target disk
 - * @update_driver_data:       Callback to update driver data on the frozen disk
 - *
 - * Helper function for low-level device drivers to check and (re) allocate and
 - * initialize a disk request queue zone bitmaps. This functions should normally
 - * be called within the disk ->revalidate method for blk-mq based drivers.
 - * Before calling this function, the device driver must already have set the
 - * device zone size (chunk_sector limit) and the max zone append limit.
 - * For BIO based drivers, this function cannot be used. BIO based device drivers
 - * only need to set disk->nr_zones so that the sysfs exposed value is correct.
 - * If the @update_driver_data callback function is not NULL, the callback is
 - * executed with the device request queue frozen after all zones have been
 - * checked.
 +static inline void blk_zone_wplug_bio_io_error(struct blk_zone_wplug *zwplug,
 +                                             struct bio *bio)
 +{
 +      struct request_queue *q = zwplug->disk->queue;
 +
 +      bio_clear_flag(bio, BIO_ZONE_WRITE_PLUGGING);
 +      bio_io_error(bio);
 +      disk_put_zone_wplug(zwplug);
 +      blk_queue_exit(q);
 +}
 +
 +/*
 + * Abort (fail) all plugged BIOs of a zone write plug.
   */
 -int blk_revalidate_disk_zones(struct gendisk *disk,
 -                            void (*update_driver_data)(struct gendisk *disk))
 +static void disk_zone_wplug_abort(struct blk_zone_wplug *zwplug)
  {
 -      struct request_queue *q = disk->queue;
 -      sector_t zone_sectors = q->limits.chunk_sectors;
 -      sector_t capacity = get_capacity(disk);
 -      struct blk_revalidate_zone_args args = { };
 -      unsigned int noio_flag;
 -      int ret;
 +      struct bio *bio;
  
 -      if (WARN_ON_ONCE(!blk_queue_is_zoned(q)))
 -              return -EIO;
 -      if (WARN_ON_ONCE(!queue_is_mq(q)))
 -              return -EIO;
 +      while ((bio = bio_list_pop(&zwplug->bio_list)))
 +              blk_zone_wplug_bio_io_error(zwplug, bio);
 +}
  
 -      if (!capacity)
 -              return -ENODEV;
 +/*
 + * Abort (fail) all plugged BIOs of a zone write plug that are not aligned
 + * with the assumed write pointer location of the zone when the BIO will
 + * be unplugged.
 + */
 +static void disk_zone_wplug_abort_unaligned(struct gendisk *disk,
 +                                          struct blk_zone_wplug *zwplug)
 +{
 +      unsigned int zone_capacity = disk->zone_capacity;
 +      unsigned int wp_offset = zwplug->wp_offset;
 +      struct bio_list bl = BIO_EMPTY_LIST;
 +      struct bio *bio;
 +
 +      while ((bio = bio_list_pop(&zwplug->bio_list))) {
 +              if (wp_offset >= zone_capacity ||
 +                  (bio_op(bio) != REQ_OP_ZONE_APPEND &&
 +                   bio_offset_from_zone_start(bio) != wp_offset)) {
 +                      blk_zone_wplug_bio_io_error(zwplug, bio);
 +                      continue;
 +              }
 +
 +              wp_offset += bio_sectors(bio);
 +              bio_list_add(&bl, bio);
 +      }
 +
 +      bio_list_merge(&zwplug->bio_list, &bl);
 +}
 +
 +static inline void disk_zone_wplug_set_error(struct gendisk *disk,
 +                                           struct blk_zone_wplug *zwplug)
 +{
 +      unsigned long flags;
 +
 +      if (zwplug->flags & BLK_ZONE_WPLUG_ERROR)
 +              return;
  
        /*
 -       * Checks that the device driver indicated a valid zone size and that
 -       * the max zone append limit is set.
 +       * At this point, we already have a reference on the zone write plug.
 +       * However, since we are going to add the plug to the disk zone write
 +       * plugs work list, increase its reference count. This reference will
 +       * be dropped in disk_zone_wplugs_work() once the error state is
 +       * handled, or in disk_zone_wplug_clear_error() if the zone is reset or
 +       * finished.
         */
 -      if (!zone_sectors || !is_power_of_2(zone_sectors)) {
 -              pr_warn("%s: Invalid non power of two zone size (%llu)\n",
 -                      disk->disk_name, zone_sectors);
 -              return -ENODEV;
 +      zwplug->flags |= BLK_ZONE_WPLUG_ERROR;
 +      atomic_inc(&zwplug->ref);
 +
 +      spin_lock_irqsave(&disk->zone_wplugs_lock, flags);
 +      list_add_tail(&zwplug->link, &disk->zone_wplugs_err_list);
 +      spin_unlock_irqrestore(&disk->zone_wplugs_lock, flags);
 +}
 +
 +static inline void disk_zone_wplug_clear_error(struct gendisk *disk,
 +                                             struct blk_zone_wplug *zwplug)
 +{
 +      unsigned long flags;
 +
 +      if (!(zwplug->flags & BLK_ZONE_WPLUG_ERROR))
 +              return;
 +
 +      /*
 +       * We are racing with the error handling work which drops the reference
 +       * on the zone write plug after handling the error state. So remove the
 +       * plug from the error list and drop its reference count only if the
 +       * error handling has not yet started, that is, if the zone write plug
 +       * is still listed.
 +       */
 +      spin_lock_irqsave(&disk->zone_wplugs_lock, flags);
 +      if (!list_empty(&zwplug->link)) {
 +              list_del_init(&zwplug->link);
 +              zwplug->flags &= ~BLK_ZONE_WPLUG_ERROR;
 +              disk_put_zone_wplug(zwplug);
        }
 +      spin_unlock_irqrestore(&disk->zone_wplugs_lock, flags);
 +}
  
 -      if (!q->limits.max_zone_append_sectors) {
 -              pr_warn("%s: Invalid 0 maximum zone append limit\n",
 -                      disk->disk_name);
 -              return -ENODEV;
 +/*
 + * Set a zone write plug write pointer offset to either 0 (zone reset case)
 + * or to the zone size (zone finish case). This aborts all plugged BIOs, which
 + * is fine to do as doing a zone reset or zone finish while writes are in-flight
 + * is a mistake from the user which will most likely cause all plugged BIOs to
 + * fail anyway.
 + */
 +static void disk_zone_wplug_set_wp_offset(struct gendisk *disk,
 +                                        struct blk_zone_wplug *zwplug,
 +                                        unsigned int wp_offset)
 +{
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&zwplug->lock, flags);
 +
 +      /*
 +       * Make sure that a BIO completion or another zone reset or finish
 +       * operation has not already removed the plug from the hash table.
 +       */
 +      if (zwplug->flags & BLK_ZONE_WPLUG_UNHASHED) {
 +              spin_unlock_irqrestore(&zwplug->lock, flags);
 +              return;
        }
  
 +      /* Update the zone write pointer and abort all plugged BIOs. */
 +      zwplug->wp_offset = wp_offset;
 +      disk_zone_wplug_abort(zwplug);
 +
        /*
 -       * Ensure that all memory allocations in this context are done as if
 -       * GFP_NOIO was specified.
 +       * Updating the write pointer offset puts back the zone
 +       * in a good state. So clear the error flag and decrement the
 +       * error count if we were in error state.
         */
 -      args.disk = disk;
 -      args.nr_zones = (capacity + zone_sectors - 1) >> ilog2(zone_sectors);
 -      noio_flag = memalloc_noio_save();
 -      ret = disk->fops->report_zones(disk, 0, UINT_MAX,
 -                                     blk_revalidate_zone_cb, &args);
 -      if (!ret) {
 -              pr_warn("%s: No zones reported\n", disk->disk_name);
 -              ret = -ENODEV;
 +      disk_zone_wplug_clear_error(disk, zwplug);
 +
 +      /*
 +       * The zone write plug now has no BIO plugged: remove it from the
 +       * hash table so that it cannot be seen. The plug will be freed
 +       * when the last reference is dropped.
 +       */
 +      if (disk_should_remove_zone_wplug(disk, zwplug))
 +              disk_remove_zone_wplug(disk, zwplug);
 +
 +      spin_unlock_irqrestore(&zwplug->lock, flags);
 +}
 +
 +static bool blk_zone_wplug_handle_reset_or_finish(struct bio *bio,
 +                                                unsigned int wp_offset)
 +{
 +      struct gendisk *disk = bio->bi_bdev->bd_disk;
 +      sector_t sector = bio->bi_iter.bi_sector;
 +      struct blk_zone_wplug *zwplug;
 +
 +      /* Conventional zones cannot be reset nor finished. */
 +      if (disk_zone_is_conv(disk, sector)) {
 +              bio_io_error(bio);
 +              return true;
        }
 -      memalloc_noio_restore(noio_flag);
  
        /*
 -       * If zones where reported, make sure that the entire disk capacity
 -       * has been checked.
 +       * If we have a zone write plug, set its write pointer offset to 0
 +       * (reset case) or to the zone size (finish case). This will abort all
 +       * BIOs plugged for the target zone. It is fine as resetting or
 +       * finishing zones while writes are still in-flight will result in the
 +       * writes failing anyway.
         */
 -      if (ret > 0 && args.sector != capacity) {
 -              pr_warn("%s: Missing zones from sector %llu\n",
 -                      disk->disk_name, args.sector);
 -              ret = -ENODEV;
 +      zwplug = disk_get_zone_wplug(disk, sector);
 +      if (zwplug) {
 +              disk_zone_wplug_set_wp_offset(disk, zwplug, wp_offset);
 +              disk_put_zone_wplug(zwplug);
        }
  
 +      return false;
 +}
 +
 +static bool blk_zone_wplug_handle_reset_all(struct bio *bio)
 +{
 +      struct gendisk *disk = bio->bi_bdev->bd_disk;
 +      struct blk_zone_wplug *zwplug;
 +      sector_t sector;
 +
        /*
 -       * Install the new bitmaps and update nr_zones only once the queue is
 -       * stopped and all I/Os are completed (i.e. a scheduler is not
 -       * referencing the bitmaps).
 +       * Set the write pointer offset of all zone write plugs to 0. This will
 +       * abort all plugged BIOs. It is fine as resetting zones while writes
 +       * are still in-flight will result in the writes failing anyway.
         */
 -      blk_mq_freeze_queue(q);
 -      if (ret > 0) {
 -              disk->nr_zones = args.nr_zones;
 -              swap(disk->seq_zones_wlock, args.seq_zones_wlock);
 -              swap(disk->conv_zones_bitmap, args.conv_zones_bitmap);
 -              if (update_driver_data)
 -                      update_driver_data(disk);
 -              ret = 0;
 -      } else {
 -              pr_warn("%s: failed to revalidate zones\n", disk->disk_name);
 -              disk_free_zone_bitmaps(disk);
 +      for (sector = 0; sector < get_capacity(disk);
 +           sector += disk->queue->limits.chunk_sectors) {
 +              zwplug = disk_get_zone_wplug(disk, sector);
 +              if (zwplug) {
 +                      disk_zone_wplug_set_wp_offset(disk, zwplug, 0);
 +                      disk_put_zone_wplug(zwplug);
 +              }
        }
 -      blk_mq_unfreeze_queue(q);
  
 -      kfree(args.seq_zones_wlock);
 -      kfree(args.conv_zones_bitmap);
 -      return ret;
 +      return false;
  }
 -EXPORT_SYMBOL_GPL(blk_revalidate_disk_zones);
 +
 +static inline void blk_zone_wplug_add_bio(struct blk_zone_wplug *zwplug,
 +                                        struct bio *bio, unsigned int nr_segs)
 +{
 +      /*
 +       * Grab an extra reference on the BIO request queue usage counter.
 +       * This reference will be reused to submit a request for the BIO for
 +       * blk-mq devices and dropped when the BIO is failed and after
 +       * it is issued in the case of BIO-based devices.
 +       */
 +      percpu_ref_get(&bio->bi_bdev->bd_disk->queue->q_usage_counter);
 +
 +      /*
 +       * The BIO is being plugged and thus will have to wait for the on-going
 +       * write and for all other writes already plugged. So polling makes
 +       * no sense.
 +       */
 +      bio_clear_polled(bio);
 +
 +      /*
 +       * Reuse the poll cookie field to store the number of segments when
 +       * split to the hardware limits.
 +       */
 +      bio->__bi_nr_segments = nr_segs;
 +
 +      /*
 +       * We always receive BIOs after they are split and ready to be issued.
 +       * The block layer passes the parts of a split BIO in order, and the
 +       * user must also issue write sequentially. So simply add the new BIO
 +       * at the tail of the list to preserve the sequential write order.
 +       */
 +      bio_list_add(&zwplug->bio_list, bio);
 +}
 +
 +/*
 + * Called from bio_attempt_back_merge() when a BIO was merged with a request.
 + */
 +void blk_zone_write_plug_bio_merged(struct bio *bio)
 +{
 +      struct blk_zone_wplug *zwplug;
 +      unsigned long flags;
 +
 +      /*
 +       * If the BIO was already plugged, then we were called through
 +       * blk_zone_write_plug_init_request() -> blk_attempt_bio_merge().
 +       * For this case, we already hold a reference on the zone write plug for
 +       * the BIO and blk_zone_write_plug_init_request() will handle the
 +       * zone write pointer offset update.
 +       */
 +      if (bio_flagged(bio, BIO_ZONE_WRITE_PLUGGING))
 +              return;
 +
 +      bio_set_flag(bio, BIO_ZONE_WRITE_PLUGGING);
 +
 +      /*
 +       * Get a reference on the zone write plug of the target zone and advance
 +       * the zone write pointer offset. Given that this is a merge, we already
 +       * have at least one request and one BIO referencing the zone write
 +       * plug. So this should not fail.
 +       */
 +      zwplug = disk_get_zone_wplug(bio->bi_bdev->bd_disk,
 +                                   bio->bi_iter.bi_sector);
 +      if (WARN_ON_ONCE(!zwplug))
 +              return;
 +
 +      spin_lock_irqsave(&zwplug->lock, flags);
 +      zwplug->wp_offset += bio_sectors(bio);
 +      spin_unlock_irqrestore(&zwplug->lock, flags);
 +}
 +
 +/*
 + * Attempt to merge plugged BIOs with a newly prepared request for a BIO that
 + * already went through zone write plugging (either a new BIO or one that was
 + * unplugged).
 + */
 +void blk_zone_write_plug_init_request(struct request *req)
 +{
 +      sector_t req_back_sector = blk_rq_pos(req) + blk_rq_sectors(req);
 +      struct request_queue *q = req->q;
 +      struct gendisk *disk = q->disk;
 +      unsigned int zone_capacity = disk->zone_capacity;
 +      struct blk_zone_wplug *zwplug =
 +              disk_get_zone_wplug(disk, blk_rq_pos(req));
 +      unsigned long flags;
 +      struct bio *bio;
 +
 +      if (WARN_ON_ONCE(!zwplug))
 +              return;
 +
 +      /*
 +       * Indicate that completion of this request needs to be handled with
 +       * blk_zone_write_plug_finish_request(), which will drop the reference
 +       * on the zone write plug we took above on entry to this function.
 +       */
 +      req->rq_flags |= RQF_ZONE_WRITE_PLUGGING;
 +
 +      if (blk_queue_nomerges(q))
 +              return;
 +
 +      /*
 +       * Walk through the list of plugged BIOs to check if they can be merged
 +       * into the back of the request.
 +       */
 +      spin_lock_irqsave(&zwplug->lock, flags);
 +      while (zwplug->wp_offset < zone_capacity) {
 +              bio = bio_list_peek(&zwplug->bio_list);
 +              if (!bio)
 +                      break;
 +
 +              if (bio->bi_iter.bi_sector != req_back_sector ||
 +                  !blk_rq_merge_ok(req, bio))
 +                      break;
 +
 +              WARN_ON_ONCE(bio_op(bio) != REQ_OP_WRITE_ZEROES &&
 +                           !bio->__bi_nr_segments);
 +
 +              bio_list_pop(&zwplug->bio_list);
 +              if (bio_attempt_back_merge(req, bio, bio->__bi_nr_segments) !=
 +                  BIO_MERGE_OK) {
 +                      bio_list_add_head(&zwplug->bio_list, bio);
 +                      break;
 +              }
 +
 +              /*
 +               * Drop the extra reference on the queue usage we got when
 +               * plugging the BIO and advance the write pointer offset.
 +               */
 +              blk_queue_exit(q);
 +              zwplug->wp_offset += bio_sectors(bio);
 +
 +              req_back_sector += bio_sectors(bio);
 +      }
 +      spin_unlock_irqrestore(&zwplug->lock, flags);
 +}
 +
 +/*
 + * Check and prepare a BIO for submission by incrementing the write pointer
 + * offset of its zone write plug and changing zone append operations into
 + * regular write when zone append emulation is needed.
 + */
 +static bool blk_zone_wplug_prepare_bio(struct blk_zone_wplug *zwplug,
 +                                     struct bio *bio)
 +{
 +      struct gendisk *disk = bio->bi_bdev->bd_disk;
 +
 +      /*
 +       * Check that the user is not attempting to write to a full zone.
 +       * We know such BIO will fail, and that would potentially overflow our
 +       * write pointer offset beyond the end of the zone.
 +       */
 +      if (zwplug->wp_offset >= disk->zone_capacity)
 +              goto err;
 +
 +      if (bio_op(bio) == REQ_OP_ZONE_APPEND) {
 +              /*
 +               * Use a regular write starting at the current write pointer.
 +               * Similarly to native zone append operations, do not allow
 +               * merging.
 +               */
 +              bio->bi_opf &= ~REQ_OP_MASK;
 +              bio->bi_opf |= REQ_OP_WRITE | REQ_NOMERGE;
 +              bio->bi_iter.bi_sector += zwplug->wp_offset;
 +
 +              /*
 +               * Remember that this BIO is in fact a zone append operation
 +               * so that we can restore its operation code on completion.
 +               */
 +              bio_set_flag(bio, BIO_EMULATES_ZONE_APPEND);
 +      } else {
 +              /*
 +               * Check for non-sequential writes early because we avoid a
 +               * whole lot of error handling trouble if we don't send it off
 +               * to the driver.
 +               */
 +              if (bio_offset_from_zone_start(bio) != zwplug->wp_offset)
 +                      goto err;
 +      }
 +
 +      /* Advance the zone write pointer offset. */
 +      zwplug->wp_offset += bio_sectors(bio);
 +
 +      return true;
 +
 +err:
 +      /* We detected an invalid write BIO: schedule error recovery. */
 +      disk_zone_wplug_set_error(disk, zwplug);
 +      kblockd_schedule_work(&disk->zone_wplugs_work);
 +      return false;
 +}
 +
 +static bool blk_zone_wplug_handle_write(struct bio *bio, unsigned int nr_segs)
 +{
 +      struct gendisk *disk = bio->bi_bdev->bd_disk;
 +      sector_t sector = bio->bi_iter.bi_sector;
 +      struct blk_zone_wplug *zwplug;
 +      gfp_t gfp_mask = GFP_NOIO;
 +      unsigned long flags;
 +
 +      /*
 +       * BIOs must be fully contained within a zone so that we use the correct
 +       * zone write plug for the entire BIO. For blk-mq devices, the block
 +       * layer should already have done any splitting required to ensure this
 +       * and this BIO should thus not be straddling zone boundaries. For
 +       * BIO-based devices, it is the responsibility of the driver to split
 +       * the bio before submitting it.
 +       */
 +      if (WARN_ON_ONCE(bio_straddles_zones(bio))) {
 +              bio_io_error(bio);
 +              return true;
 +      }
 +
 +      /* Conventional zones do not need write plugging. */
 +      if (disk_zone_is_conv(disk, sector)) {
 +              /* Zone append to conventional zones is not allowed. */
 +              if (bio_op(bio) == REQ_OP_ZONE_APPEND) {
 +                      bio_io_error(bio);
 +                      return true;
 +              }
 +              return false;
 +      }
 +
 +      if (bio->bi_opf & REQ_NOWAIT)
 +              gfp_mask = GFP_NOWAIT;
 +
 +      zwplug = disk_get_and_lock_zone_wplug(disk, sector, gfp_mask, &flags);
 +      if (!zwplug) {
 +              bio_io_error(bio);
 +              return true;
 +      }
 +
 +      /* Indicate that this BIO is being handled using zone write plugging. */
 +      bio_set_flag(bio, BIO_ZONE_WRITE_PLUGGING);
 +
 +      /*
 +       * If the zone is already plugged or has a pending error, add the BIO
 +       * to the plug BIO list. Otherwise, plug and let the BIO execute.
 +       */
 +      if (zwplug->flags & BLK_ZONE_WPLUG_BUSY)
 +              goto plug;
 +
 +      /*
 +       * If an error is detected when preparing the BIO, add it to the BIO
 +       * list so that error recovery can deal with it.
 +       */
 +      if (!blk_zone_wplug_prepare_bio(zwplug, bio))
 +              goto plug;
 +
 +      zwplug->flags |= BLK_ZONE_WPLUG_PLUGGED;
 +
 +      spin_unlock_irqrestore(&zwplug->lock, flags);
 +
 +      return false;
 +
 +plug:
 +      zwplug->flags |= BLK_ZONE_WPLUG_PLUGGED;
 +      blk_zone_wplug_add_bio(zwplug, bio, nr_segs);
 +
 +      spin_unlock_irqrestore(&zwplug->lock, flags);
 +
 +      return true;
 +}
 +
 +/**
 + * blk_zone_plug_bio - Handle a zone write BIO with zone write plugging
 + * @bio: The BIO being submitted
 + * @nr_segs: The number of physical segments of @bio
 + *
 + * Handle write, write zeroes and zone append operations requiring emulation
 + * using zone write plugging.
 + *
 + * Return true whenever @bio execution needs to be delayed through the zone
 + * write plug. Otherwise, return false to let the submission path process
 + * @bio normally.
 + */
 +bool blk_zone_plug_bio(struct bio *bio, unsigned int nr_segs)
 +{
 +      struct block_device *bdev = bio->bi_bdev;
 +
 +      if (!bdev->bd_disk->zone_wplugs_hash)
 +              return false;
 +
 +      /*
 +       * If the BIO already has the plugging flag set, then it was already
 +       * handled through this path and this is a submission from the zone
 +       * plug bio submit work.
 +       */
 +      if (bio_flagged(bio, BIO_ZONE_WRITE_PLUGGING))
 +              return false;
 +
 +      /*
 +       * We do not need to do anything special for empty flush BIOs, e.g
 +       * BIOs such as issued by blkdev_issue_flush(). The is because it is
 +       * the responsibility of the user to first wait for the completion of
 +       * write operations for flush to have any effect on the persistence of
 +       * the written data.
 +       */
 +      if (op_is_flush(bio->bi_opf) && !bio_sectors(bio))
 +              return false;
 +
 +      /*
 +       * Regular writes and write zeroes need to be handled through the target
 +       * zone write plug. This includes writes with REQ_FUA | REQ_PREFLUSH
 +       * which may need to go through the flush machinery depending on the
 +       * target device capabilities. Plugging such writes is fine as the flush
 +       * machinery operates at the request level, below the plug, and
 +       * completion of the flush sequence will go through the regular BIO
 +       * completion, which will handle zone write plugging.
 +       * Zone append operations for devices that requested emulation must
 +       * also be plugged so that these BIOs can be changed into regular
 +       * write BIOs.
 +       * Zone reset, reset all and finish commands need special treatment
 +       * to correctly track the write pointer offset of zones. These commands
 +       * are not plugged as we do not need serialization with write
 +       * operations. It is the responsibility of the user to not issue reset
 +       * and finish commands when write operations are in flight.
 +       */
 +      switch (bio_op(bio)) {
 +      case REQ_OP_ZONE_APPEND:
 +              if (!bdev_emulates_zone_append(bdev))
 +                      return false;
 +              fallthrough;
 +      case REQ_OP_WRITE:
 +      case REQ_OP_WRITE_ZEROES:
 +              return blk_zone_wplug_handle_write(bio, nr_segs);
 +      case REQ_OP_ZONE_RESET:
 +              return blk_zone_wplug_handle_reset_or_finish(bio, 0);
 +      case REQ_OP_ZONE_FINISH:
 +              return blk_zone_wplug_handle_reset_or_finish(bio,
 +                                              bdev_zone_sectors(bdev));
 +      case REQ_OP_ZONE_RESET_ALL:
 +              return blk_zone_wplug_handle_reset_all(bio);
 +      default:
 +              return false;
 +      }
 +
 +      return false;
 +}
 +EXPORT_SYMBOL_GPL(blk_zone_plug_bio);
 +
 +static void disk_zone_wplug_schedule_bio_work(struct gendisk *disk,
 +                                            struct blk_zone_wplug *zwplug)
 +{
 +      /*
 +       * Take a reference on the zone write plug and schedule the submission
 +       * of the next plugged BIO. blk_zone_wplug_bio_work() will release the
 +       * reference we take here.
 +       */
 +      WARN_ON_ONCE(!(zwplug->flags & BLK_ZONE_WPLUG_PLUGGED));
 +      atomic_inc(&zwplug->ref);
 +      queue_work(disk->zone_wplugs_wq, &zwplug->bio_work);
 +}
 +
 +static void disk_zone_wplug_unplug_bio(struct gendisk *disk,
 +                                     struct blk_zone_wplug *zwplug)
 +{
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&zwplug->lock, flags);
 +
 +      /*
 +       * If we had an error, schedule error recovery. The recovery work
 +       * will restart submission of plugged BIOs.
 +       */
 +      if (zwplug->flags & BLK_ZONE_WPLUG_ERROR) {
 +              spin_unlock_irqrestore(&zwplug->lock, flags);
 +              kblockd_schedule_work(&disk->zone_wplugs_work);
 +              return;
 +      }
 +
 +      /* Schedule submission of the next plugged BIO if we have one. */
 +      if (!bio_list_empty(&zwplug->bio_list)) {
 +              disk_zone_wplug_schedule_bio_work(disk, zwplug);
 +              spin_unlock_irqrestore(&zwplug->lock, flags);
 +              return;
 +      }
 +
 +      zwplug->flags &= ~BLK_ZONE_WPLUG_PLUGGED;
 +
 +      /*
 +       * If the zone is full (it was fully written or finished, or empty
 +       * (it was reset), remove its zone write plug from the hash table.
 +       */
 +      if (disk_should_remove_zone_wplug(disk, zwplug))
 +              disk_remove_zone_wplug(disk, zwplug);
 +
 +      spin_unlock_irqrestore(&zwplug->lock, flags);
 +}
 +
 +void blk_zone_write_plug_bio_endio(struct bio *bio)
 +{
 +      struct gendisk *disk = bio->bi_bdev->bd_disk;
 +      struct blk_zone_wplug *zwplug =
 +              disk_get_zone_wplug(disk, bio->bi_iter.bi_sector);
 +      unsigned long flags;
 +
 +      if (WARN_ON_ONCE(!zwplug))
 +              return;
 +
 +      /* Make sure we do not see this BIO again by clearing the plug flag. */
 +      bio_clear_flag(bio, BIO_ZONE_WRITE_PLUGGING);
 +
 +      /*
 +       * If this is a regular write emulating a zone append operation,
 +       * restore the original operation code.
 +       */
 +      if (bio_flagged(bio, BIO_EMULATES_ZONE_APPEND)) {
 +              bio->bi_opf &= ~REQ_OP_MASK;
 +              bio->bi_opf |= REQ_OP_ZONE_APPEND;
 +      }
 +
 +      /*
 +       * If the BIO failed, mark the plug as having an error to trigger
 +       * recovery.
 +       */
 +      if (bio->bi_status != BLK_STS_OK) {
 +              spin_lock_irqsave(&zwplug->lock, flags);
 +              disk_zone_wplug_set_error(disk, zwplug);
 +              spin_unlock_irqrestore(&zwplug->lock, flags);
 +      }
 +
 +      /* Drop the reference we took when the BIO was issued. */
 +      disk_put_zone_wplug(zwplug);
 +
 +      /*
 +       * For BIO-based devices, blk_zone_write_plug_finish_request()
 +       * is not called. So we need to schedule execution of the next
 +       * plugged BIO here.
 +       */
-       if (bio->bi_bdev->bd_has_submit_bio)
++      if (bdev_test_flag(bio->bi_bdev, BD_HAS_SUBMIT_BIO))
 +              disk_zone_wplug_unplug_bio(disk, zwplug);
 +
 +      /* Drop the reference we took when entering this function. */
 +      disk_put_zone_wplug(zwplug);
 +}
 +
 +void blk_zone_write_plug_finish_request(struct request *req)
 +{
 +      struct gendisk *disk = req->q->disk;
 +      struct blk_zone_wplug *zwplug;
 +
 +      zwplug = disk_get_zone_wplug(disk, req->__sector);
 +      if (WARN_ON_ONCE(!zwplug))
 +              return;
 +
 +      req->rq_flags &= ~RQF_ZONE_WRITE_PLUGGING;
 +
 +      /*
 +       * Drop the reference we took when the request was initialized in
 +       * blk_zone_write_plug_init_request().
 +       */
 +      disk_put_zone_wplug(zwplug);
 +
 +      disk_zone_wplug_unplug_bio(disk, zwplug);
 +
 +      /* Drop the reference we took when entering this function. */
 +      disk_put_zone_wplug(zwplug);
 +}
 +
 +static void blk_zone_wplug_bio_work(struct work_struct *work)
 +{
 +      struct blk_zone_wplug *zwplug =
 +              container_of(work, struct blk_zone_wplug, bio_work);
 +      struct block_device *bdev;
 +      unsigned long flags;
 +      struct bio *bio;
 +
 +      /*
 +       * Submit the next plugged BIO. If we do not have any, clear
 +       * the plugged flag.
 +       */
 +      spin_lock_irqsave(&zwplug->lock, flags);
 +
 +      bio = bio_list_pop(&zwplug->bio_list);
 +      if (!bio) {
 +              zwplug->flags &= ~BLK_ZONE_WPLUG_PLUGGED;
 +              spin_unlock_irqrestore(&zwplug->lock, flags);
 +              goto put_zwplug;
 +      }
 +
 +      if (!blk_zone_wplug_prepare_bio(zwplug, bio)) {
 +              /* Error recovery will decide what to do with the BIO. */
 +              bio_list_add_head(&zwplug->bio_list, bio);
 +              spin_unlock_irqrestore(&zwplug->lock, flags);
 +              goto put_zwplug;
 +      }
 +
 +      spin_unlock_irqrestore(&zwplug->lock, flags);
 +
 +      bdev = bio->bi_bdev;
 +      submit_bio_noacct_nocheck(bio);
 +
 +      /*
 +       * blk-mq devices will reuse the extra reference on the request queue
 +       * usage counter we took when the BIO was plugged, but the submission
 +       * path for BIO-based devices will not do that. So drop this extra
 +       * reference here.
 +       */
-       if (bdev->bd_has_submit_bio)
++      if (bdev_test_flag(bdev, BD_HAS_SUBMIT_BIO))
 +              blk_queue_exit(bdev->bd_disk->queue);
 +
 +put_zwplug:
 +      /* Drop the reference we took in disk_zone_wplug_schedule_bio_work(). */
 +      disk_put_zone_wplug(zwplug);
 +}
 +
 +static unsigned int blk_zone_wp_offset(struct blk_zone *zone)
 +{
 +      switch (zone->cond) {
 +      case BLK_ZONE_COND_IMP_OPEN:
 +      case BLK_ZONE_COND_EXP_OPEN:
 +      case BLK_ZONE_COND_CLOSED:
 +              return zone->wp - zone->start;
 +      case BLK_ZONE_COND_FULL:
 +              return zone->len;
 +      case BLK_ZONE_COND_EMPTY:
 +              return 0;
 +      case BLK_ZONE_COND_NOT_WP:
 +      case BLK_ZONE_COND_OFFLINE:
 +      case BLK_ZONE_COND_READONLY:
 +      default:
 +              /*
 +               * Conventional, offline and read-only zones do not have a valid
 +               * write pointer.
 +               */
 +              return UINT_MAX;
 +      }
 +}
 +
 +static int blk_zone_wplug_report_zone_cb(struct blk_zone *zone,
 +                                       unsigned int idx, void *data)
 +{
 +      struct blk_zone *zonep = data;
 +
 +      *zonep = *zone;
 +      return 0;
 +}
 +
 +static void disk_zone_wplug_handle_error(struct gendisk *disk,
 +                                       struct blk_zone_wplug *zwplug)
 +{
 +      sector_t zone_start_sector =
 +              bdev_zone_sectors(disk->part0) * zwplug->zone_no;
 +      unsigned int noio_flag;
 +      struct blk_zone zone;
 +      unsigned long flags;
 +      int ret;
 +
 +      /* Get the current zone information from the device. */
 +      noio_flag = memalloc_noio_save();
 +      ret = disk->fops->report_zones(disk, zone_start_sector, 1,
 +                                     blk_zone_wplug_report_zone_cb, &zone);
 +      memalloc_noio_restore(noio_flag);
 +
 +      spin_lock_irqsave(&zwplug->lock, flags);
 +
 +      /*
 +       * A zone reset or finish may have cleared the error already. In such
 +       * case, do nothing as the report zones may have seen the "old" write
 +       * pointer value before the reset/finish operation completed.
 +       */
 +      if (!(zwplug->flags & BLK_ZONE_WPLUG_ERROR))
 +              goto unlock;
 +
 +      zwplug->flags &= ~BLK_ZONE_WPLUG_ERROR;
 +
 +      if (ret != 1) {
 +              /*
 +               * We failed to get the zone information, meaning that something
 +               * is likely really wrong with the device. Abort all remaining
 +               * plugged BIOs as otherwise we could endup waiting forever on
 +               * plugged BIOs to complete if there is a queue freeze on-going.
 +               */
 +              disk_zone_wplug_abort(zwplug);
 +              goto unplug;
 +      }
 +
 +      /* Update the zone write pointer offset. */
 +      zwplug->wp_offset = blk_zone_wp_offset(&zone);
 +      disk_zone_wplug_abort_unaligned(disk, zwplug);
 +
 +      /* Restart BIO submission if we still have any BIO left. */
 +      if (!bio_list_empty(&zwplug->bio_list)) {
 +              disk_zone_wplug_schedule_bio_work(disk, zwplug);
 +              goto unlock;
 +      }
 +
 +unplug:
 +      zwplug->flags &= ~BLK_ZONE_WPLUG_PLUGGED;
 +      if (disk_should_remove_zone_wplug(disk, zwplug))
 +              disk_remove_zone_wplug(disk, zwplug);
 +
 +unlock:
 +      spin_unlock_irqrestore(&zwplug->lock, flags);
 +}
 +
 +static void disk_zone_wplugs_work(struct work_struct *work)
 +{
 +      struct gendisk *disk =
 +              container_of(work, struct gendisk, zone_wplugs_work);
 +      struct blk_zone_wplug *zwplug;
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&disk->zone_wplugs_lock, flags);
 +
 +      while (!list_empty(&disk->zone_wplugs_err_list)) {
 +              zwplug = list_first_entry(&disk->zone_wplugs_err_list,
 +                                        struct blk_zone_wplug, link);
 +              list_del_init(&zwplug->link);
 +              spin_unlock_irqrestore(&disk->zone_wplugs_lock, flags);
 +
 +              disk_zone_wplug_handle_error(disk, zwplug);
 +              disk_put_zone_wplug(zwplug);
 +
 +              spin_lock_irqsave(&disk->zone_wplugs_lock, flags);
 +      }
 +
 +      spin_unlock_irqrestore(&disk->zone_wplugs_lock, flags);
 +}
 +
 +static inline unsigned int disk_zone_wplugs_hash_size(struct gendisk *disk)
 +{
 +      return 1U << disk->zone_wplugs_hash_bits;
 +}
 +
 +void disk_init_zone_resources(struct gendisk *disk)
 +{
 +      spin_lock_init(&disk->zone_wplugs_lock);
 +      INIT_LIST_HEAD(&disk->zone_wplugs_err_list);
 +      INIT_WORK(&disk->zone_wplugs_work, disk_zone_wplugs_work);
 +}
 +
 +/*
 + * For the size of a disk zone write plug hash table, use the size of the
 + * zone write plug mempool, which is the maximum of the disk open zones and
 + * active zones limits. But do not exceed 4KB (512 hlist head entries), that is,
 + * 9 bits. For a disk that has no limits, mempool size defaults to 128.
 + */
 +#define BLK_ZONE_WPLUG_MAX_HASH_BITS          9
 +#define BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE      128
 +
 +static int disk_alloc_zone_resources(struct gendisk *disk,
 +                                   unsigned int pool_size)
 +{
 +      unsigned int i;
 +
 +      disk->zone_wplugs_hash_bits =
 +              min(ilog2(pool_size) + 1, BLK_ZONE_WPLUG_MAX_HASH_BITS);
 +
 +      disk->zone_wplugs_hash =
 +              kcalloc(disk_zone_wplugs_hash_size(disk),
 +                      sizeof(struct hlist_head), GFP_KERNEL);
 +      if (!disk->zone_wplugs_hash)
 +              return -ENOMEM;
 +
 +      for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++)
 +              INIT_HLIST_HEAD(&disk->zone_wplugs_hash[i]);
 +
 +      disk->zone_wplugs_pool = mempool_create_kmalloc_pool(pool_size,
 +                                              sizeof(struct blk_zone_wplug));
 +      if (!disk->zone_wplugs_pool)
 +              goto free_hash;
 +
 +      disk->zone_wplugs_wq =
 +              alloc_workqueue("%s_zwplugs", WQ_MEM_RECLAIM | WQ_HIGHPRI,
 +                              pool_size, disk->disk_name);
 +      if (!disk->zone_wplugs_wq)
 +              goto destroy_pool;
 +
 +      return 0;
 +
 +destroy_pool:
 +      mempool_destroy(disk->zone_wplugs_pool);
 +      disk->zone_wplugs_pool = NULL;
 +free_hash:
 +      kfree(disk->zone_wplugs_hash);
 +      disk->zone_wplugs_hash = NULL;
 +      disk->zone_wplugs_hash_bits = 0;
 +      return -ENOMEM;
 +}
 +
 +static void disk_destroy_zone_wplugs_hash_table(struct gendisk *disk)
 +{
 +      struct blk_zone_wplug *zwplug;
 +      unsigned int i;
 +
 +      if (!disk->zone_wplugs_hash)
 +              return;
 +
 +      /* Free all the zone write plugs we have. */
 +      for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++) {
 +              while (!hlist_empty(&disk->zone_wplugs_hash[i])) {
 +                      zwplug = hlist_entry(disk->zone_wplugs_hash[i].first,
 +                                           struct blk_zone_wplug, node);
 +                      atomic_inc(&zwplug->ref);
 +                      disk_remove_zone_wplug(disk, zwplug);
 +                      disk_put_zone_wplug(zwplug);
 +              }
 +      }
 +
 +      kfree(disk->zone_wplugs_hash);
 +      disk->zone_wplugs_hash = NULL;
 +      disk->zone_wplugs_hash_bits = 0;
 +}
 +
 +void disk_free_zone_resources(struct gendisk *disk)
 +{
 +      cancel_work_sync(&disk->zone_wplugs_work);
 +
 +      if (disk->zone_wplugs_wq) {
 +              destroy_workqueue(disk->zone_wplugs_wq);
 +              disk->zone_wplugs_wq = NULL;
 +      }
 +
 +      disk_destroy_zone_wplugs_hash_table(disk);
 +
 +      /*
 +       * Wait for the zone write plugs to be RCU-freed before
 +       * destorying the mempool.
 +       */
 +      rcu_barrier();
 +
 +      mempool_destroy(disk->zone_wplugs_pool);
 +      disk->zone_wplugs_pool = NULL;
 +
 +      kfree(disk->conv_zones_bitmap);
 +      disk->conv_zones_bitmap = NULL;
 +      disk->zone_capacity = 0;
 +      disk->nr_zones = 0;
 +}
 +
 +static inline bool disk_need_zone_resources(struct gendisk *disk)
 +{
 +      /*
 +       * All mq zoned devices need zone resources so that the block layer
 +       * can automatically handle write BIO plugging. BIO-based device drivers
 +       * (e.g. DM devices) are normally responsible for handling zone write
 +       * ordering and do not need zone resources, unless the driver requires
 +       * zone append emulation.
 +       */
 +      return queue_is_mq(disk->queue) ||
 +              queue_emulates_zone_append(disk->queue);
 +}
 +
 +static int disk_revalidate_zone_resources(struct gendisk *disk,
 +                                        unsigned int nr_zones)
 +{
 +      struct queue_limits *lim = &disk->queue->limits;
 +      unsigned int pool_size;
 +
 +      if (!disk_need_zone_resources(disk))
 +              return 0;
 +
 +      /*
 +       * If the device has no limit on the maximum number of open and active
 +       * zones, use BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE.
 +       */
 +      pool_size = max(lim->max_open_zones, lim->max_active_zones);
 +      if (!pool_size)
 +              pool_size = min(BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE, nr_zones);
 +
 +      if (!disk->zone_wplugs_hash)
 +              return disk_alloc_zone_resources(disk, pool_size);
 +
 +      return 0;
 +}
 +
 +struct blk_revalidate_zone_args {
 +      struct gendisk  *disk;
 +      unsigned long   *conv_zones_bitmap;
 +      unsigned int    nr_zones;
 +      unsigned int    zone_capacity;
 +      sector_t        sector;
 +};
 +
 +/*
 + * Update the disk zone resources information and device queue limits.
 + * The disk queue is frozen when this is executed.
 + */
 +static int disk_update_zone_resources(struct gendisk *disk,
 +                                    struct blk_revalidate_zone_args *args)
 +{
 +      struct request_queue *q = disk->queue;
 +      unsigned int nr_seq_zones, nr_conv_zones = 0;
 +      unsigned int pool_size;
 +      struct queue_limits lim;
 +
 +      disk->nr_zones = args->nr_zones;
 +      disk->zone_capacity = args->zone_capacity;
 +      swap(disk->conv_zones_bitmap, args->conv_zones_bitmap);
 +      if (disk->conv_zones_bitmap)
 +              nr_conv_zones = bitmap_weight(disk->conv_zones_bitmap,
 +                                            disk->nr_zones);
 +      if (nr_conv_zones >= disk->nr_zones) {
 +              pr_warn("%s: Invalid number of conventional zones %u / %u\n",
 +                      disk->disk_name, nr_conv_zones, disk->nr_zones);
 +              return -ENODEV;
 +      }
 +
 +      if (!disk->zone_wplugs_pool)
 +              return 0;
 +
 +      /*
 +       * If the device has no limit on the maximum number of open and active
 +       * zones, set its max open zone limit to the mempool size to indicate
 +       * to the user that there is a potential performance impact due to
 +       * dynamic zone write plug allocation when simultaneously writing to
 +       * more zones than the size of the mempool.
 +       */
 +      lim = queue_limits_start_update(q);
 +
 +      nr_seq_zones = disk->nr_zones - nr_conv_zones;
 +      pool_size = max(lim.max_open_zones, lim.max_active_zones);
 +      if (!pool_size)
 +              pool_size = min(BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE, nr_seq_zones);
 +
 +      mempool_resize(disk->zone_wplugs_pool, pool_size);
 +
 +      if (!lim.max_open_zones && !lim.max_active_zones) {
 +              if (pool_size < nr_seq_zones)
 +                      lim.max_open_zones = pool_size;
 +              else
 +                      lim.max_open_zones = 0;
 +      }
 +
 +      return queue_limits_commit_update(q, &lim);
 +}
 +
 +static int blk_revalidate_conv_zone(struct blk_zone *zone, unsigned int idx,
 +                                  struct blk_revalidate_zone_args *args)
 +{
 +      struct gendisk *disk = args->disk;
 +      struct request_queue *q = disk->queue;
 +
 +      if (zone->capacity != zone->len) {
 +              pr_warn("%s: Invalid conventional zone capacity\n",
 +                      disk->disk_name);
 +              return -ENODEV;
 +      }
 +
 +      if (!disk_need_zone_resources(disk))
 +              return 0;
 +
 +      if (!args->conv_zones_bitmap) {
 +              args->conv_zones_bitmap =
 +                      blk_alloc_zone_bitmap(q->node, args->nr_zones);
 +              if (!args->conv_zones_bitmap)
 +                      return -ENOMEM;
 +      }
 +
 +      set_bit(idx, args->conv_zones_bitmap);
 +
 +      return 0;
 +}
 +
 +static int blk_revalidate_seq_zone(struct blk_zone *zone, unsigned int idx,
 +                                 struct blk_revalidate_zone_args *args)
 +{
 +      struct gendisk *disk = args->disk;
 +      struct blk_zone_wplug *zwplug;
 +      unsigned int wp_offset;
 +      unsigned long flags;
 +
 +      /*
 +       * Remember the capacity of the first sequential zone and check
 +       * if it is constant for all zones.
 +       */
 +      if (!args->zone_capacity)
 +              args->zone_capacity = zone->capacity;
 +      if (zone->capacity != args->zone_capacity) {
 +              pr_warn("%s: Invalid variable zone capacity\n",
 +                      disk->disk_name);
 +              return -ENODEV;
 +      }
 +
 +      /*
 +       * We need to track the write pointer of all zones that are not
 +       * empty nor full. So make sure we have a zone write plug for
 +       * such zone if the device has a zone write plug hash table.
 +       */
 +      if (!disk->zone_wplugs_hash)
 +              return 0;
 +
 +      wp_offset = blk_zone_wp_offset(zone);
 +      if (!wp_offset || wp_offset >= zone->capacity)
 +              return 0;
 +
 +      zwplug = disk_get_and_lock_zone_wplug(disk, zone->wp, GFP_NOIO, &flags);
 +      if (!zwplug)
 +              return -ENOMEM;
 +      spin_unlock_irqrestore(&zwplug->lock, flags);
 +      disk_put_zone_wplug(zwplug);
 +
 +      return 0;
 +}
 +
 +/*
 + * Helper function to check the validity of zones of a zoned block device.
 + */
 +static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,
 +                                void *data)
 +{
 +      struct blk_revalidate_zone_args *args = data;
 +      struct gendisk *disk = args->disk;
 +      sector_t capacity = get_capacity(disk);
 +      sector_t zone_sectors = disk->queue->limits.chunk_sectors;
 +      int ret;
 +
 +      /* Check for bad zones and holes in the zone report */
 +      if (zone->start != args->sector) {
 +              pr_warn("%s: Zone gap at sectors %llu..%llu\n",
 +                      disk->disk_name, args->sector, zone->start);
 +              return -ENODEV;
 +      }
 +
 +      if (zone->start >= capacity || !zone->len) {
 +              pr_warn("%s: Invalid zone start %llu, length %llu\n",
 +                      disk->disk_name, zone->start, zone->len);
 +              return -ENODEV;
 +      }
 +
 +      /*
 +       * All zones must have the same size, with the exception on an eventual
 +       * smaller last zone.
 +       */
 +      if (zone->start + zone->len < capacity) {
 +              if (zone->len != zone_sectors) {
 +                      pr_warn("%s: Invalid zoned device with non constant zone size\n",
 +                              disk->disk_name);
 +                      return -ENODEV;
 +              }
 +      } else if (zone->len > zone_sectors) {
 +              pr_warn("%s: Invalid zoned device with larger last zone size\n",
 +                      disk->disk_name);
 +              return -ENODEV;
 +      }
 +
 +      if (!zone->capacity || zone->capacity > zone->len) {
 +              pr_warn("%s: Invalid zone capacity\n",
 +                      disk->disk_name);
 +              return -ENODEV;
 +      }
 +
 +      /* Check zone type */
 +      switch (zone->type) {
 +      case BLK_ZONE_TYPE_CONVENTIONAL:
 +              ret = blk_revalidate_conv_zone(zone, idx, args);
 +              break;
 +      case BLK_ZONE_TYPE_SEQWRITE_REQ:
 +              ret = blk_revalidate_seq_zone(zone, idx, args);
 +              break;
 +      case BLK_ZONE_TYPE_SEQWRITE_PREF:
 +      default:
 +              pr_warn("%s: Invalid zone type 0x%x at sectors %llu\n",
 +                      disk->disk_name, (int)zone->type, zone->start);
 +              ret = -ENODEV;
 +      }
 +
 +      if (!ret)
 +              args->sector += zone->len;
 +
 +      return ret;
 +}
 +
 +/**
 + * blk_revalidate_disk_zones - (re)allocate and initialize zone write plugs
 + * @disk:     Target disk
 + *
 + * Helper function for low-level device drivers to check, (re) allocate and
 + * initialize resources used for managing zoned disks. This function should
 + * normally be called by blk-mq based drivers when a zoned gendisk is probed
 + * and when the zone configuration of the gendisk changes (e.g. after a format).
 + * Before calling this function, the device driver must already have set the
 + * device zone size (chunk_sector limit) and the max zone append limit.
 + * BIO based drivers can also use this function as long as the device queue
 + * can be safely frozen.
 + */
 +int blk_revalidate_disk_zones(struct gendisk *disk)
 +{
 +      struct request_queue *q = disk->queue;
 +      sector_t zone_sectors = q->limits.chunk_sectors;
 +      sector_t capacity = get_capacity(disk);
 +      struct blk_revalidate_zone_args args = { };
 +      unsigned int noio_flag;
 +      int ret = -ENOMEM;
 +
 +      if (WARN_ON_ONCE(!blk_queue_is_zoned(q)))
 +              return -EIO;
 +
 +      if (!capacity)
 +              return -ENODEV;
 +
 +      /*
 +       * Checks that the device driver indicated a valid zone size and that
 +       * the max zone append limit is set.
 +       */
 +      if (!zone_sectors || !is_power_of_2(zone_sectors)) {
 +              pr_warn("%s: Invalid non power of two zone size (%llu)\n",
 +                      disk->disk_name, zone_sectors);
 +              return -ENODEV;
 +      }
 +
 +      if (!queue_max_zone_append_sectors(q)) {
 +              pr_warn("%s: Invalid 0 maximum zone append limit\n",
 +                      disk->disk_name);
 +              return -ENODEV;
 +      }
 +
 +      /*
 +       * Ensure that all memory allocations in this context are done as if
 +       * GFP_NOIO was specified.
 +       */
 +      args.disk = disk;
 +      args.nr_zones = (capacity + zone_sectors - 1) >> ilog2(zone_sectors);
 +      noio_flag = memalloc_noio_save();
 +      ret = disk_revalidate_zone_resources(disk, args.nr_zones);
 +      if (ret) {
 +              memalloc_noio_restore(noio_flag);
 +              return ret;
 +      }
 +      ret = disk->fops->report_zones(disk, 0, UINT_MAX,
 +                                     blk_revalidate_zone_cb, &args);
 +      if (!ret) {
 +              pr_warn("%s: No zones reported\n", disk->disk_name);
 +              ret = -ENODEV;
 +      }
 +      memalloc_noio_restore(noio_flag);
 +
 +      /*
 +       * If zones where reported, make sure that the entire disk capacity
 +       * has been checked.
 +       */
 +      if (ret > 0 && args.sector != capacity) {
 +              pr_warn("%s: Missing zones from sector %llu\n",
 +                      disk->disk_name, args.sector);
 +              ret = -ENODEV;
 +      }
 +
 +      /*
 +       * Set the new disk zone parameters only once the queue is frozen and
 +       * all I/Os are completed.
 +       */
 +      blk_mq_freeze_queue(q);
 +      if (ret > 0)
 +              ret = disk_update_zone_resources(disk, &args);
 +      else
 +              pr_warn("%s: failed to revalidate zones\n", disk->disk_name);
 +      if (ret)
 +              disk_free_zone_resources(disk);
 +      blk_mq_unfreeze_queue(q);
 +
 +      kfree(args.conv_zones_bitmap);
 +
 +      return ret;
 +}
 +EXPORT_SYMBOL_GPL(blk_revalidate_disk_zones);
 +
 +#ifdef CONFIG_BLK_DEBUG_FS
 +
 +int queue_zone_wplugs_show(void *data, struct seq_file *m)
 +{
 +      struct request_queue *q = data;
 +      struct gendisk *disk = q->disk;
 +      struct blk_zone_wplug *zwplug;
 +      unsigned int zwp_wp_offset, zwp_flags;
 +      unsigned int zwp_zone_no, zwp_ref;
 +      unsigned int zwp_bio_list_size, i;
 +      unsigned long flags;
 +
 +      if (!disk->zone_wplugs_hash)
 +              return 0;
 +
 +      rcu_read_lock();
 +      for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++) {
 +              hlist_for_each_entry_rcu(zwplug,
 +                                       &disk->zone_wplugs_hash[i], node) {
 +                      spin_lock_irqsave(&zwplug->lock, flags);
 +                      zwp_zone_no = zwplug->zone_no;
 +                      zwp_flags = zwplug->flags;
 +                      zwp_ref = atomic_read(&zwplug->ref);
 +                      zwp_wp_offset = zwplug->wp_offset;
 +                      zwp_bio_list_size = bio_list_size(&zwplug->bio_list);
 +                      spin_unlock_irqrestore(&zwplug->lock, flags);
 +
 +                      seq_printf(m, "%u 0x%x %u %u %u\n",
 +                                 zwp_zone_no, zwp_flags, zwp_ref,
 +                                 zwp_wp_offset, zwp_bio_list_size);
 +              }
 +      }
 +      rcu_read_unlock();
 +
 +      return 0;
 +}
 +
 +#endif
diff --cc block/genhd.c
Simple merge
diff --cc block/ioctl.c
Simple merge
Simple merge
index d88c0a009483592e677fd450c243547ee5545aec,5bb7805927ac14cafcda860ae35e044be56698ed..781c4500491bde29f6c6d858c2b224e814bfb6ba
@@@ -45,12 -45,17 +45,17 @@@ struct block_device 
        struct request_queue *  bd_queue;
        struct disk_stats __percpu *bd_stats;
        unsigned long           bd_stamp;
-       bool                    bd_read_only;   /* read-only policy */
-       u8                      bd_partno;
-       bool                    bd_write_holder;
-       bool                    bd_has_submit_bio;
+       atomic_t                __bd_flags;     // partition number + flags
+ #define BD_PARTNO             255     // lower 8 bits; assign-once
+ #define BD_READ_ONLY          (1u<<8) // read-only policy
+ #define BD_WRITE_HOLDER               (1u<<9)
+ #define BD_HAS_SUBMIT_BIO     (1u<<10)
+ #define BD_RO_WARNED          (1u<<11)
+ #ifdef CONFIG_FAIL_MAKE_REQUEST
+ #define BD_MAKE_IT_FAIL               (1u<<12)
+ #endif
        dev_t                   bd_dev;
 -      struct inode            *bd_inode;      /* will die */
 +      struct address_space    *bd_mapping;    /* page cache */
  
        atomic_t                bd_openers;
        spinlock_t              bd_size_lock; /* for bd_inode->i_size updates */
Simple merge