Zoned block devices have no write constraints for conventional zones
so write locking of conventional zones is not necessary and can hurt
performance. To avoid this, introduce the seq_zones bitmap to indicate
if a zone is a sequential one. Use this information to allow any write
to be issued to conventional zones, locking only sequential zones.

As the zone bitmap allocation for seq_zones is identical to the zones
write lock bitmap, introduce the helper sd_zbc_alloc_zone_bitmap().
Using this helper, wait for the disk capacity and number of zones to
stabilize on the second revalidation pass to allocate and initialize
the bitmaps.

Signed-off-by: Damien Le Moal <damien.lem...@wdc.com>
Reviewed-by: Bart Van Assche <bart.vanass...@wdc.com>
---
 drivers/scsi/sd.h     |   1 +
 drivers/scsi/sd_zbc.c | 108 ++++++++++++++++++++++++++++++++++++++++++++------
 drivers/scsi/sd_zbc.h |  12 ++++++
 3 files changed, 108 insertions(+), 13 deletions(-)

diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h
index c9a627e7eebd..3ea82c8cecd5 100644
--- a/drivers/scsi/sd.h
+++ b/drivers/scsi/sd.h
@@ -77,6 +77,7 @@ struct scsi_disk {
        unsigned int    zone_blocks;
        unsigned int    zone_shift;
        unsigned long   *zones_wlock;
+       unsigned long   *seq_zones;
        unsigned int    zones_optimal_open;
        unsigned int    zones_optimal_nonseq;
        unsigned int    zones_max_open;
diff --git a/drivers/scsi/sd_zbc.c b/drivers/scsi/sd_zbc.c
index 8b258254a2bb..bcece82a1d8d 100644
--- a/drivers/scsi/sd_zbc.c
+++ b/drivers/scsi/sd_zbc.c
@@ -252,7 +252,7 @@ int sd_zbc_write_lock_zone(struct scsi_cmnd *cmd)
        struct scsi_disk *sdkp = scsi_disk(rq->rq_disk);
        sector_t sector = blk_rq_pos(rq);
        sector_t zone_sectors = sd_zbc_zone_sectors(sdkp);
-       unsigned int zno = sd_zbc_zone_no(sdkp, sector);
+       unsigned int zno;
 
        /*
         * Note: Checks of the alignment of the write command on
@@ -265,13 +265,15 @@ int sd_zbc_write_lock_zone(struct scsi_cmnd *cmd)
                return BLKPREP_KILL;
 
        /*
-        * Do not issue more than one write at a time per
-        * zone. This solves write ordering problems due to
-        * the unlocking of the request queue in the dispatch
-        * path in the non scsi-mq case.
+        * There is no write constraint on conventional zones, but do not issue
+        * more than one write at a time per sequential zone. This avoids write
+        * ordering problems due to the unlocking of the request queue in the
+        * dispatch path of the non scsi-mq (legacy) case.
         */
-       if (sdkp->zones_wlock &&
-           test_and_set_bit(zno, sdkp->zones_wlock))
+       zno = sd_zbc_zone_no(sdkp, sector);
+       if (!test_bit(zno, sdkp->seq_zones))
+               return BLKPREP_OK;
+       if (test_and_set_bit(zno, sdkp->zones_wlock))
                return BLKPREP_DEFER;
 
        WARN_ON_ONCE(cmd->flags & SCMD_ZONE_WRITE_LOCK);
@@ -289,8 +291,9 @@ void sd_zbc_write_unlock_zone(struct scsi_cmnd *cmd)
        struct request *rq = cmd->request;
        struct scsi_disk *sdkp = scsi_disk(rq->rq_disk);
 
-       if (sdkp->zones_wlock && cmd->flags & SCMD_ZONE_WRITE_LOCK) {
+       if (cmd->flags & SCMD_ZONE_WRITE_LOCK) {
                unsigned int zno = sd_zbc_zone_no(sdkp, blk_rq_pos(rq));
+
                WARN_ON_ONCE(!test_bit(zno, sdkp->zones_wlock));
                cmd->flags &= ~SCMD_ZONE_WRITE_LOCK;
                clear_bit_unlock(zno, sdkp->zones_wlock);
@@ -507,8 +510,67 @@ static int sd_zbc_check_zone_size(struct scsi_disk *sdkp)
        return 0;
 }
 
+/*
+ * Initialize the sequential zone bitmap to allow identifying sequential zones.
+ */
+static int sd_zbc_setup_seq_zones(struct scsi_disk *sdkp)
+{
+       unsigned long *seq_zones;
+       sector_t block = 0;
+       unsigned char *buf;
+       unsigned char *rec;
+       unsigned int buf_len;
+       unsigned int list_length;
+       unsigned int n = 0;
+       int ret = -ENOMEM;
+
+       /* Allocate zone type bitmap */
+       seq_zones = sd_zbc_alloc_zone_bitmap(sdkp);
+       if (!seq_zones)
+               return -ENOMEM;
+
+       buf = kmalloc(SD_ZBC_BUF_SIZE, GFP_KERNEL);
+       if (!buf)
+               goto out;
+
+       while (block < sdkp->capacity) {
+
+               ret = sd_zbc_report_zones(sdkp, buf, SD_ZBC_BUF_SIZE, block);
+               if (ret)
+                       goto out;
+
+               /* Parse reported zone descriptors */
+               list_length = get_unaligned_be32(&buf[0]) + 64;
+               rec = buf + 64;
+               buf_len = min(list_length, SD_ZBC_BUF_SIZE);
+               while (rec < buf + buf_len) {
+                       if ((rec[0] & 0x0f) != ZBC_ZONE_TYPE_CONV)
+                               set_bit(n, seq_zones);
+                       block += get_unaligned_be64(&rec[8]);
+                       rec += 64;
+                       n++;
+               }
+
+       }
+
+       if (n != sdkp->nr_zones)
+               ret = -EIO;
+
+out:
+       kfree(buf);
+       if (ret) {
+               kfree(seq_zones);
+               return ret;
+       }
+
+       sdkp->seq_zones = seq_zones;
+
+       return 0;
+}
+
 static int sd_zbc_setup(struct scsi_disk *sdkp)
 {
+       int ret;
 
        /* READ16/WRITE16 is mandatory for ZBC disks */
        sdkp->device->use_16_for_rw = 1;
@@ -520,17 +582,37 @@ static int sd_zbc_setup(struct scsi_disk *sdkp)
        sdkp->nr_zones =
                round_up(sdkp->capacity, sdkp->zone_blocks) >> sdkp->zone_shift;
 
+       /*
+        * Wait for the disk capacity to stabilize before
+        * initializing zone related information.
+        */
+       if (sdkp->first_scan)
+               return 0;
+
        if (!sdkp->zones_wlock) {
-               sdkp->zones_wlock = kcalloc(BITS_TO_LONGS(sdkp->nr_zones),
-                                           sizeof(unsigned long),
-                                           GFP_KERNEL);
+               sdkp->zones_wlock = sd_zbc_alloc_zone_bitmap(sdkp);
                if (!sdkp->zones_wlock)
                        return -ENOMEM;
        }
 
+       if (!sdkp->seq_zones) {
+               ret = sd_zbc_setup_seq_zones(sdkp);
+               if (ret)
+                       return ret;
+       }
+
        return 0;
 }
 
+static void sd_zbc_cleanup(struct scsi_disk *sdkp)
+{
+       kfree(sdkp->seq_zones);
+       sdkp->seq_zones = NULL;
+
+       kfree(sdkp->zones_wlock);
+       sdkp->zones_wlock = NULL;
+}
+
 int sd_zbc_read_zones(struct scsi_disk *sdkp, unsigned char *buf)
 {
        int ret;
@@ -582,14 +664,14 @@ int sd_zbc_read_zones(struct scsi_disk *sdkp, unsigned 
char *buf)
 
 err:
        sdkp->capacity = 0;
+       sd_zbc_cleanup(sdkp);
 
        return ret;
 }
 
 void sd_zbc_remove(struct scsi_disk *sdkp)
 {
-       kfree(sdkp->zones_wlock);
-       sdkp->zones_wlock = NULL;
+       sd_zbc_cleanup(sdkp);
 }
 
 void sd_zbc_print_zones(struct scsi_disk *sdkp)
diff --git a/drivers/scsi/sd_zbc.h b/drivers/scsi/sd_zbc.h
index b34002f0acbe..2b63ee352fa2 100644
--- a/drivers/scsi/sd_zbc.h
+++ b/drivers/scsi/sd_zbc.h
@@ -32,6 +32,18 @@ static inline unsigned int sd_zbc_zone_no(struct scsi_disk 
*sdkp,
        return sectors_to_logical(sdkp->device, sector) >> sdkp->zone_shift;
 }
 
+/*
+ * Allocate a zone bitmap (one bit per zone).
+ */
+static inline unsigned long *sd_zbc_alloc_zone_bitmap(struct scsi_disk *sdkp)
+{
+       struct request_queue *q = sdkp->disk->queue;
+
+       return kzalloc_node(BITS_TO_LONGS(sdkp->nr_zones)
+                           * sizeof(unsigned long),
+                           GFP_KERNEL, q->node);
+}
+
 #else /* CONFIG_BLK_DEV_ZONED */
 
 static inline int sd_zbc_read_zones(struct scsi_disk *sdkp,
-- 
2.13.5

Reply via email to