zbd: ensure that global max open zones limit is respected
[fio.git] / zbd.c
diff --git a/zbd.c b/zbd.c
index 6a26fe108a68acf55c7ad2b7e59e8fccb97f0dc8..04c68dea28c64bb51b850f7df4fad0953713ad3d 100644 (file)
--- a/zbd.c
+++ b/zbd.c
@@ -32,6 +32,17 @@ int zbd_get_zoned_model(struct thread_data *td, struct fio_file *f,
 {
        int ret;
 
+       if (f->filetype == FIO_TYPE_PIPE) {
+               log_err("zonemode=zbd does not support pipes\n");
+               return -EINVAL;
+       }
+
+       /* If regular file, always emulate zones inside the file. */
+       if (f->filetype == FIO_TYPE_FILE) {
+               *model = ZBD_NONE;
+               return 0;
+       }
+
        if (td->io_ops && td->io_ops->get_zoned_model)
                ret = td->io_ops->get_zoned_model(td, f, model);
        else
@@ -113,6 +124,34 @@ int zbd_reset_wp(struct thread_data *td, struct fio_file *f,
        return ret;
 }
 
+/**
+ * zbd_get_max_open_zones - Get the maximum number of open zones
+ * @td: FIO thread data
+ * @f: FIO file for which to get max open zones
+ * @max_open_zones: Upon success, result will be stored here.
+ *
+ * A @max_open_zones value set to zero means no limit.
+ *
+ * Returns 0 upon success and a negative error code upon failure.
+ */
+int zbd_get_max_open_zones(struct thread_data *td, struct fio_file *f,
+                          unsigned int *max_open_zones)
+{
+       int ret;
+
+       if (td->io_ops && td->io_ops->get_max_open_zones)
+               ret = td->io_ops->get_max_open_zones(td, f, max_open_zones);
+       else
+               ret = blkzoned_get_max_open_zones(td, f, max_open_zones);
+       if (ret < 0) {
+               td_verror(td, errno, "get max open zones failed");
+               log_err("%s: get max open zones failed (%d).\n",
+                       f->file_name, errno);
+       }
+
+       return ret;
+}
+
 /**
  * zbd_zone_idx - convert an offset into a zone number
  * @f: file pointer.
@@ -285,9 +324,7 @@ static bool zbd_verify_sizes(void)
                                return false;
                        }
 
-                       if (td->o.zone_skip &&
-                           (td->o.zone_skip < td->o.zone_size ||
-                            td->o.zone_skip % td->o.zone_size)) {
+                       if (td->o.zone_skip % td->o.zone_size) {
                                log_err("%s: zoneskip %llu is not a multiple of the device zone size %llu.\n",
                                        f->file_name, (unsigned long long) td->o.zone_skip,
                                        (unsigned long long) td->o.zone_size);
@@ -335,20 +372,21 @@ static bool zbd_verify_bs(void)
 {
        struct thread_data *td;
        struct fio_file *f;
-       uint32_t zone_size;
        int i, j, k;
 
        for_each_td(td, i) {
                for_each_file(td, f, j) {
+                       uint64_t zone_size;
+
                        if (!f->zbd_info)
                                continue;
                        zone_size = f->zbd_info->zone_size;
                        for (k = 0; k < FIO_ARRAY_SIZE(td->o.bs); k++) {
                                if (td->o.verify != VERIFY_NONE &&
                                    zone_size % td->o.bs[k] != 0) {
-                                       log_info("%s: block size %llu is not a divisor of the zone size %d\n",
+                                       log_info("%s: block size %llu is not a divisor of the zone size %llu\n",
                                                 f->file_name, td->o.bs[k],
-                                                zone_size);
+                                                (unsigned long long)zone_size);
                                        return false;
                                }
                        }
@@ -382,7 +420,7 @@ static int init_zone_info(struct thread_data *td, struct fio_file *f)
        int i;
 
        if (zone_size == 0) {
-               log_err("%s: Specifying the zone size is mandatory for regular block devices with --zonemode=zbd\n\n",
+               log_err("%s: Specifying the zone size is mandatory for regular file/block device with --zonemode=zbd\n\n",
                        f->file_name);
                return 1;
        }
@@ -403,6 +441,12 @@ static int init_zone_info(struct thread_data *td, struct fio_file *f)
                return 1;
        }
 
+       if (f->real_file_size < zone_size) {
+               log_err("%s: file/device size %"PRIu64" is smaller than zone size %"PRIu64"\n",
+                       f->file_name, f->real_file_size, zone_size);
+               return -EINVAL;
+       }
+
        nr_zones = (f->real_file_size + zone_size - 1) / zone_size;
        zbd_info = scalloc(1, sizeof(*zbd_info) +
                           (nr_zones + 1) * sizeof(zbd_info->zone_info[0]));
@@ -555,6 +599,55 @@ out:
        return ret;
 }
 
+static int zbd_set_max_open_zones(struct thread_data *td, struct fio_file *f)
+{
+       struct zoned_block_device_info *zbd = f->zbd_info;
+       unsigned int max_open_zones;
+       int ret;
+
+       if (zbd->model != ZBD_HOST_MANAGED || td->o.ignore_zone_limits) {
+               /* Only host-managed devices have a max open limit */
+               zbd->max_open_zones = td->o.max_open_zones;
+               goto out;
+       }
+
+       /* If host-managed, get the max open limit */
+       ret = zbd_get_max_open_zones(td, f, &max_open_zones);
+       if (ret)
+               return ret;
+
+       if (!max_open_zones) {
+               /* No device limit */
+               zbd->max_open_zones = td->o.max_open_zones;
+       } else if (!td->o.max_open_zones) {
+               /* No user limit. Set limit to device limit */
+               zbd->max_open_zones = max_open_zones;
+       } else if (td->o.max_open_zones <= max_open_zones) {
+               /* Both user limit and dev limit. User limit not too large */
+               zbd->max_open_zones = td->o.max_open_zones;
+       } else {
+               /* Both user limit and dev limit. User limit too large */
+               td_verror(td, EINVAL,
+                         "Specified --max_open_zones is too large");
+               log_err("Specified --max_open_zones (%d) is larger than max (%u)\n",
+                       td->o.max_open_zones, max_open_zones);
+               return -EINVAL;
+       }
+
+out:
+       /* Ensure that the limit is not larger than FIO's internal limit */
+       if (zbd->max_open_zones > ZBD_MAX_OPEN_ZONES) {
+               td_verror(td, EINVAL, "'max_open_zones' value is too large");
+               log_err("'max_open_zones' value is larger than %u\n", ZBD_MAX_OPEN_ZONES);
+               return -EINVAL;
+       }
+
+       dprint(FD_ZBD, "%s: using max open zones limit: %"PRIu32"\n",
+              f->file_name, zbd->max_open_zones);
+
+       return 0;
+}
+
 /*
  * Allocate zone information and store it into f->zbd_info if zonemode=zbd.
  *
@@ -572,14 +665,16 @@ static int zbd_create_zone_info(struct thread_data *td, struct fio_file *f)
                return ret;
 
        switch (zbd_model) {
-       case ZBD_IGNORE:
-               return 0;
        case ZBD_HOST_AWARE:
        case ZBD_HOST_MANAGED:
                ret = parse_zone_info(td, f);
+               if (ret)
+                       return ret;
                break;
        case ZBD_NONE:
                ret = init_zone_info(td, f);
+               if (ret)
+                       return ret;
                break;
        default:
                td_verror(td, EINVAL, "Unsupported zoned model");
@@ -587,11 +682,16 @@ static int zbd_create_zone_info(struct thread_data *td, struct fio_file *f)
                return -EINVAL;
        }
 
-       if (ret == 0) {
-               f->zbd_info->model = zbd_model;
-               f->zbd_info->max_open_zones = td->o.max_open_zones;
+       assert(f->zbd_info);
+       f->zbd_info->model = zbd_model;
+
+       ret = zbd_set_max_open_zones(td, f);
+       if (ret) {
+               zbd_free_zone_info(f);
+               return ret;
        }
-       return ret;
+
+       return 0;
 }
 
 void zbd_free_zone_info(struct fio_file *f)
@@ -648,7 +748,7 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f,
 static int zbd_reset_zone(struct thread_data *td, struct fio_file *f,
                          struct fio_zone_info *z);
 
-int zbd_setup_files(struct thread_data *td)
+int zbd_init_files(struct thread_data *td)
 {
        struct fio_file *f;
        int i;
@@ -657,6 +757,44 @@ int zbd_setup_files(struct thread_data *td)
                if (zbd_init_zone_info(td, f))
                        return 1;
        }
+       return 0;
+}
+
+void zbd_recalc_options_with_zone_granularity(struct thread_data *td)
+{
+       struct fio_file *f;
+       int i;
+
+       for_each_file(td, f, i) {
+               struct zoned_block_device_info *zbd = f->zbd_info;
+               // zonemode=strided doesn't get per-file zone size.
+               uint64_t zone_size = zbd ? zbd->zone_size : td->o.zone_size;
+
+               if (zone_size == 0)
+                       continue;
+
+               if (td->o.size_nz > 0) {
+                       td->o.size = td->o.size_nz * zone_size;
+               }
+               if (td->o.io_size_nz > 0) {
+                       td->o.io_size = td->o.io_size_nz * zone_size;
+               }
+               if (td->o.start_offset_nz > 0) {
+                       td->o.start_offset = td->o.start_offset_nz * zone_size;
+               }
+               if (td->o.offset_increment_nz > 0) {
+                       td->o.offset_increment = td->o.offset_increment_nz * zone_size;
+               }
+               if (td->o.zone_skip_nz > 0) {
+                       td->o.zone_skip = td->o.zone_skip_nz * zone_size;
+               }
+       }
+}
+
+int zbd_setup_files(struct thread_data *td)
+{
+       struct fio_file *f;
+       int i;
 
        if (!zbd_using_direct_io()) {
                log_err("Using direct I/O is mandatory for writing to ZBD drives\n\n");
@@ -674,8 +812,7 @@ int zbd_setup_files(struct thread_data *td)
                struct fio_zone_info *z;
                int zi;
 
-               if (!zbd)
-                       continue;
+               assert(zbd);
 
                f->min_zone = zbd_zone_idx(f, f->file_offset);
                f->max_zone = zbd_zone_idx(f, f->file_offset + f->io_size);
@@ -689,18 +826,30 @@ int zbd_setup_files(struct thread_data *td)
                if (zbd_is_seq_job(f))
                        assert(f->min_zone < f->max_zone);
 
-               zbd->max_open_zones = zbd->max_open_zones ?: ZBD_MAX_OPEN_ZONES;
-
                if (td->o.max_open_zones > 0 &&
                    zbd->max_open_zones != td->o.max_open_zones) {
                        log_err("Different 'max_open_zones' values\n");
                        return 1;
                }
-               if (zbd->max_open_zones > ZBD_MAX_OPEN_ZONES) {
-                       log_err("'max_open_zones' value is limited by %u\n", ZBD_MAX_OPEN_ZONES);
+
+               /*
+                * The per job max open zones limit cannot be used without a
+                * global max open zones limit. (As the tracking of open zones
+                * is disabled when there is no global max open zones limit.)
+                */
+               if (td->o.job_max_open_zones && !zbd->max_open_zones) {
+                       log_err("'job_max_open_zones' cannot be used without a global open zones limit\n");
                        return 1;
                }
 
+               /*
+                * zbd->max_open_zones is the global limit shared for all jobs
+                * that target the same zoned block device. Force sync the per
+                * thread global limit with the actual global limit. (The real
+                * per thread/job limit is stored in td->o.job_max_open_zones).
+                */
+               td->o.max_open_zones = zbd->max_open_zones;
+
                for (zi = f->min_zone; zi < f->max_zone; zi++) {
                        z = &zbd->zone_info[zi];
                        if (z->cond != ZBD_ZONE_COND_IMP_OPEN &&
@@ -805,16 +954,13 @@ static void zbd_close_zone(struct thread_data *td, const struct fio_file *f,
  * @f: fio file for which to reset zones
  * @zb: first zone to reset.
  * @ze: first zone not to reset.
- * @all_zones: whether to reset all zones or only those zones for which the
- *     write pointer is not a multiple of td->o.min_bs[DDIR_WRITE].
  */
 static int zbd_reset_zones(struct thread_data *td, struct fio_file *f,
                           struct fio_zone_info *const zb,
-                          struct fio_zone_info *const ze, bool all_zones)
+                          struct fio_zone_info *const ze)
 {
        struct fio_zone_info *z;
        const uint32_t min_bs = td->o.min_bs[DDIR_WRITE];
-       bool reset_wp;
        int res = 0;
 
        assert(min_bs);
@@ -827,16 +973,10 @@ static int zbd_reset_zones(struct thread_data *td, struct fio_file *f,
                if (!z->has_wp)
                        continue;
                zone_lock(td, f, z);
-               if (all_zones) {
-                       pthread_mutex_lock(&f->zbd_info->mutex);
-                       zbd_close_zone(td, f, nz);
-                       pthread_mutex_unlock(&f->zbd_info->mutex);
-
-                       reset_wp = z->wp != z->start;
-               } else {
-                       reset_wp = z->wp % min_bs != 0;
-               }
-               if (reset_wp) {
+               pthread_mutex_lock(&f->zbd_info->mutex);
+               zbd_close_zone(td, f, nz);
+               pthread_mutex_unlock(&f->zbd_info->mutex);
+               if (z->wp != z->start) {
                        dprint(FD_ZBD, "%s: resetting zone %u\n",
                               f->file_name, zbd_zone_nr(f, z));
                        if (zbd_reset_zone(td, f, z) < 0)
@@ -959,8 +1099,8 @@ void zbd_file_reset(struct thread_data *td, struct fio_file *f)
         * writing any data to avoid that a zone reset has to be issued while
         * writing data, which causes data loss.
         */
-       zbd_reset_zones(td, f, zb, ze, td->o.verify != VERIFY_NONE &&
-                       td->runstate != TD_VERIFYING);
+       if (td->o.verify != VERIFY_NONE && td->runstate != TD_VERIFYING)
+               zbd_reset_zones(td, f, zb, ze);
        zbd_reset_write_cnt(td, f);
 }
 
@@ -971,6 +1111,8 @@ static bool is_zone_open(const struct thread_data *td, const struct fio_file *f,
        struct zoned_block_device_info *zbdi = f->zbd_info;
        int i;
 
+       /* This function should never be called when zbdi->max_open_zones == 0 */
+       assert(zbdi->max_open_zones);
        assert(td->o.job_max_open_zones == 0 || td->num_open_zones <= td->o.job_max_open_zones);
        assert(td->o.job_max_open_zones <= zbdi->max_open_zones);
        assert(zbdi->num_open_zones <= zbdi->max_open_zones);
@@ -992,6 +1134,7 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f,
                          uint32_t zone_idx)
 {
        const uint32_t min_bs = td->o.min_bs[DDIR_WRITE];
+       struct zoned_block_device_info *zbdi = f->zbd_info;
        struct fio_zone_info *z = get_zone(f, zone_idx);
        bool res = true;
 
@@ -1005,7 +1148,15 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f,
        if (td->o.verify != VERIFY_NONE && zbd_zone_full(f, z, min_bs))
                return false;
 
-       pthread_mutex_lock(&f->zbd_info->mutex);
+       /*
+        * zbdi->max_open_zones == 0 means that there is no limit on the maximum
+        * number of open zones. In this case, do no track open zones in
+        * zbdi->open_zones array.
+        */
+       if (!zbdi->max_open_zones)
+               return true;
+
+       pthread_mutex_lock(&zbdi->mutex);
        if (is_zone_open(td, f, zone_idx)) {
                /*
                 * If the zone is already open and going to be full by writes
@@ -1020,16 +1171,16 @@ static bool zbd_open_zone(struct thread_data *td, const struct fio_file *f,
        if (td->o.job_max_open_zones > 0 &&
            td->num_open_zones >= td->o.job_max_open_zones)
                goto out;
-       if (f->zbd_info->num_open_zones >= f->zbd_info->max_open_zones)
+       if (zbdi->num_open_zones >= zbdi->max_open_zones)
                goto out;
        dprint(FD_ZBD, "%s: opening zone %d\n", f->file_name, zone_idx);
-       f->zbd_info->open_zones[f->zbd_info->num_open_zones++] = zone_idx;
+       zbdi->open_zones[zbdi->num_open_zones++] = zone_idx;
        td->num_open_zones++;
        z->open = 1;
        res = true;
 
 out:
-       pthread_mutex_unlock(&f->zbd_info->mutex);
+       pthread_mutex_unlock(&zbdi->mutex);
        return res;
 }
 
@@ -1053,6 +1204,7 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td,
 {
        const uint32_t min_bs = td->o.min_bs[io_u->ddir];
        struct fio_file *f = io_u->file;
+       struct zoned_block_device_info *zbdi = f->zbd_info;
        struct fio_zone_info *z;
        unsigned int open_zone_idx = -1;
        uint32_t zone_idx, new_zone_idx;
@@ -1061,12 +1213,12 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td,
 
        assert(is_valid_offset(f, io_u->offset));
 
-       if (td->o.max_open_zones || td->o.job_max_open_zones) {
+       if (zbdi->max_open_zones || td->o.job_max_open_zones) {
                /*
-                * This statement accesses f->zbd_info->open_zones[] on purpose
+                * This statement accesses zbdi->open_zones[] on purpose
                 * without locking.
                 */
-               zone_idx = f->zbd_info->open_zones[pick_random_zone_idx(f, io_u)];
+               zone_idx = zbdi->open_zones[pick_random_zone_idx(f, io_u)];
        } else {
                zone_idx = zbd_zone_idx(f, io_u->offset);
        }
@@ -1078,9 +1230,9 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td,
               __func__, f->file_name, zone_idx, io_u->offset, io_u->buflen);
 
        /*
-        * Since z->mutex is the outer lock and f->zbd_info->mutex the inner
+        * Since z->mutex is the outer lock and zbdi->mutex the inner
         * lock it can happen that the state of the zone with index zone_idx
-        * has changed after 'z' has been assigned and before f->zbd_info->mutex
+        * has changed after 'z' has been assigned and before zbdi->mutex
         * has been obtained. Hence the loop.
         */
        for (;;) {
@@ -1089,12 +1241,12 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td,
                z = get_zone(f, zone_idx);
                if (z->has_wp)
                        zone_lock(td, f, z);
-               pthread_mutex_lock(&f->zbd_info->mutex);
+               pthread_mutex_lock(&zbdi->mutex);
                if (z->has_wp) {
                        if (z->cond != ZBD_ZONE_COND_OFFLINE &&
-                           td->o.max_open_zones == 0 && td->o.job_max_open_zones == 0)
+                           zbdi->max_open_zones == 0 && td->o.job_max_open_zones == 0)
                                goto examine_zone;
-                       if (f->zbd_info->num_open_zones == 0) {
+                       if (zbdi->num_open_zones == 0) {
                                dprint(FD_ZBD, "%s(%s): no zones are open\n",
                                       __func__, f->file_name);
                                goto open_other_zone;
@@ -1108,14 +1260,14 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td,
                 */
                open_zone_idx = pick_random_zone_idx(f, io_u);
                assert(!open_zone_idx ||
-                      open_zone_idx < f->zbd_info->num_open_zones);
+                      open_zone_idx < zbdi->num_open_zones);
                tmp_idx = open_zone_idx;
-               for (i = 0; i < f->zbd_info->num_open_zones; i++) {
+               for (i = 0; i < zbdi->num_open_zones; i++) {
                        uint32_t tmpz;
 
-                       if (tmp_idx >= f->zbd_info->num_open_zones)
+                       if (tmp_idx >= zbdi->num_open_zones)
                                tmp_idx = 0;
-                       tmpz = f->zbd_info->open_zones[tmp_idx];
+                       tmpz = zbdi->open_zones[tmp_idx];
                        if (f->min_zone <= tmpz && tmpz < f->max_zone) {
                                open_zone_idx = tmp_idx;
                                goto found_candidate_zone;
@@ -1126,39 +1278,39 @@ static struct fio_zone_info *zbd_convert_to_open_zone(struct thread_data *td,
 
                dprint(FD_ZBD, "%s(%s): no candidate zone\n",
                        __func__, f->file_name);
-               pthread_mutex_unlock(&f->zbd_info->mutex);
+               pthread_mutex_unlock(&zbdi->mutex);
                if (z->has_wp)
                        zone_unlock(z);
                return NULL;
 
 found_candidate_zone:
-               new_zone_idx = f->zbd_info->open_zones[open_zone_idx];
+               new_zone_idx = zbdi->open_zones[open_zone_idx];
                if (new_zone_idx == zone_idx)
                        break;
                zone_idx = new_zone_idx;
-               pthread_mutex_unlock(&f->zbd_info->mutex);
+               pthread_mutex_unlock(&zbdi->mutex);
                if (z->has_wp)
                        zone_unlock(z);
        }
 
-       /* Both z->mutex and f->zbd_info->mutex are held. */
+       /* Both z->mutex and zbdi->mutex are held. */
 
 examine_zone:
        if (z->wp + min_bs <= zbd_zone_capacity_end(z)) {
-               pthread_mutex_unlock(&f->zbd_info->mutex);
+               pthread_mutex_unlock(&zbdi->mutex);
                goto out;
        }
 
 open_other_zone:
        /* Check if number of open zones reaches one of limits. */
        wait_zone_close =
-               f->zbd_info->num_open_zones == f->max_zone - f->min_zone ||
-               (td->o.max_open_zones &&
-                f->zbd_info->num_open_zones == td->o.max_open_zones) ||
+               zbdi->num_open_zones == f->max_zone - f->min_zone ||
+               (zbdi->max_open_zones &&
+                zbdi->num_open_zones == zbdi->max_open_zones) ||
                (td->o.job_max_open_zones &&
                 td->num_open_zones == td->o.job_max_open_zones);
 
-       pthread_mutex_unlock(&f->zbd_info->mutex);
+       pthread_mutex_unlock(&zbdi->mutex);
 
        /* Only z->mutex is held. */
 
@@ -1173,7 +1325,7 @@ open_other_zone:
        }
 
        /* Zone 'z' is full, so try to open a new zone. */
-       for (i = f->io_size / f->zbd_info->zone_size; i > 0; i--) {
+       for (i = f->io_size / zbdi->zone_size; i > 0; i--) {
                zone_idx++;
                if (z->has_wp)
                        zone_unlock(z);
@@ -1196,12 +1348,12 @@ open_other_zone:
        /* Only z->mutex is held. */
 
        /* Check whether the write fits in any of the already opened zones. */
-       pthread_mutex_lock(&f->zbd_info->mutex);
-       for (i = 0; i < f->zbd_info->num_open_zones; i++) {
-               zone_idx = f->zbd_info->open_zones[i];
+       pthread_mutex_lock(&zbdi->mutex);
+       for (i = 0; i < zbdi->num_open_zones; i++) {
+               zone_idx = zbdi->open_zones[i];
                if (zone_idx < f->min_zone || zone_idx >= f->max_zone)
                        continue;
-               pthread_mutex_unlock(&f->zbd_info->mutex);
+               pthread_mutex_unlock(&zbdi->mutex);
                zone_unlock(z);
 
                z = get_zone(f, zone_idx);
@@ -1209,9 +1361,9 @@ open_other_zone:
                zone_lock(td, f, z);
                if (z->wp + min_bs <= zbd_zone_capacity_end(z))
                        goto out;
-               pthread_mutex_lock(&f->zbd_info->mutex);
+               pthread_mutex_lock(&zbdi->mutex);
        }
-       pthread_mutex_unlock(&f->zbd_info->mutex);
+       pthread_mutex_unlock(&zbdi->mutex);
        zone_unlock(z);
        dprint(FD_ZBD, "%s(%s): did not open another zone\n", __func__,
               f->file_name);
@@ -1347,8 +1499,7 @@ static void zbd_queue_io(struct thread_data *td, struct io_u *io_u, int q,
        uint32_t zone_idx;
        uint64_t zone_end;
 
-       if (!zbd_info)
-               return;
+       assert(zbd_info);
 
        zone_idx = zbd_zone_idx(f, io_u->offset);
        assert(zone_idx < zbd_info->nr_zones);
@@ -1408,8 +1559,7 @@ static void zbd_put_io(struct thread_data *td, const struct io_u *io_u)
        struct fio_zone_info *z;
        uint32_t zone_idx;
 
-       if (!zbd_info)
-               return;
+       assert(zbd_info);
 
        zone_idx = zbd_zone_idx(f, io_u->offset);
        assert(zone_idx < zbd_info->nr_zones);
@@ -1465,6 +1615,7 @@ void setup_zbd_zone_mode(struct thread_data *td, struct io_u *io_u)
 
        assert(td->o.zone_mode == ZONE_MODE_ZBD);
        assert(td->o.zone_size);
+       assert(f->zbd_info);
 
        zone_idx = zbd_zone_idx(f, f->last_pos[ddir]);
        z = get_zone(f, zone_idx);
@@ -1539,6 +1690,7 @@ enum fio_ddir zbd_adjust_ddir(struct thread_data *td, struct io_u *io_u,
         * devices with all empty zones. Overwrite the first I/O direction as
         * write to make sure data to read exists.
         */
+       assert(io_u->file->zbd_info);
        if (ddir != DDIR_READ || !td_rw(td))
                return ddir;
 
@@ -1561,6 +1713,7 @@ enum fio_ddir zbd_adjust_ddir(struct thread_data *td, struct io_u *io_u,
 enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
 {
        struct fio_file *f = io_u->file;
+       struct zoned_block_device_info *zbdi = f->zbd_info;
        uint32_t zone_idx_b;
        struct fio_zone_info *zb, *zl, *orig_zb;
        uint32_t orig_len = io_u->buflen;
@@ -1568,9 +1721,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
        uint64_t new_len;
        int64_t range;
 
-       if (!f->zbd_info)
-               return io_u_accept;
-
+       assert(zbdi);
        assert(min_bs);
        assert(is_valid_offset(f, io_u->offset));
        assert(io_u->buflen);
@@ -1682,12 +1833,12 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
                assert(io_u->offset + io_u->buflen <= zb->wp);
                goto accept;
        case DDIR_WRITE:
-               if (io_u->buflen > f->zbd_info->zone_size) {
+               if (io_u->buflen > zbdi->zone_size) {
                        td_verror(td, EINVAL, "I/O buflen exceeds zone size");
                        dprint(FD_IO,
                               "%s: I/O buflen %llu exceeds zone size %llu\n",
                               f->file_name, io_u->buflen,
-                              (unsigned long long) f->zbd_info->zone_size);
+                              (unsigned long long) zbdi->zone_size);
                        goto eof;
                }
                if (!zbd_open_zone(td, f, zone_idx_b)) {
@@ -1702,7 +1853,7 @@ enum io_u_action zbd_adjust_block(struct thread_data *td, struct io_u *io_u)
                }
                /* Check whether the zone reset threshold has been exceeded */
                if (td->o.zrf.u.f) {
-                       if (f->zbd_info->wp_sectors_with_data >=
+                       if (zbdi->wp_sectors_with_data >=
                            f->io_size * td->o.zrt.u.f &&
                            zbd_dec_and_reset_write_cnt(td, f)) {
                                zb->reset_zone = 1;