On 8/26/26 05:57, Niklas Cassel wrote:
> The write pointers of a zoned device outlive any particular use of it,
> whether the device keeps them itself or a backend records them, while the
> logical block size is a property of the frontend and is chosen afresh
> every time the device is attached. Nothing ties the two together. A zone
> written while the device was configured with logical_block_size=512
> leaves a write pointer that is a multiple of 512, and attaching the same
> device with logical_block_size=4096 makes that pointer unaddressable.
> 
> Such a pointer is not merely misaligned. The guest addresses the device
> in logical blocks, and a zone report expresses the write pointer in 512
> byte sectors, so the guest is told about a position that does not fall on
> a logical block boundary. It can neither read nor write there, and the
> zone can only be recovered by resetting it. The reverse direction is
> harmless: a pointer laid down with a larger logical block size is still a
> multiple of a smaller one.
> 
> On a zoned null_blk device with a logical block size of 512, a 512 byte
> append to a sequential zone leaves the write pointer half a logical block
> into it:
> 
>   $ qemu-io --image-opts -n driver=host_device,filename=/dev/nullb0 \
>         -c "zap -p 0x20000000 0x200" -c "zrp 0x20000000 1"
>   start: 0x100000, len 0x80000, cap 0x80000, wptr 0x100001, zcond:2
> 
> Attaching that disk with logical_block_size=4096 handed the guest a zone
> it could not write to.
> 
> Check at realize time that the zone size and every write pointer of a
> sequential zone are multiples of the write granularity that the device is
> about to report, and refuse to start otherwise. The write pointers are
> already held in memory by the driver, so this costs no I/O.
> 
> The check uses blkconf_zone_write_granularity(), the same value that a
> frontend reports to its guest and validates requests against, so the
> three cannot disagree.
> 
> Signed-off-by: Niklas Cassel <[email protected]>
> ---
>  hw/block/block.c         | 46 ++++++++++++++++++++++++++++++++++++++++
>  hw/block/virtio-blk.c    |  4 ++++
>  include/hw/block/block.h |  1 +
>  3 files changed, 51 insertions(+)
> 
> diff --git a/hw/block/block.c b/hw/block/block.c
> index 1c3135843d..b908e0ef64 100644
> --- a/hw/block/block.c
> +++ b/hw/block/block.c
> @@ -208,6 +208,52 @@ uint32_t blkconf_zone_write_granularity(BlockConf *conf)
>      return MAX(bs->bl.write_granularity, conf->logical_block_size);
>  }
>  
> +bool blkconf_zoned(BlockConf *conf, Error **errp)
> +{
> +    BlockDriverState *bs = blk_bs(conf->blk);
> +    uint32_t wg;
> +
> +    if (bs->bl.zoned == BLK_Z_NONE) {

This test and the name of the function are making things very confusing...
Maybe rename the function: blkconf_zoned_alignement_ok() or something like that?

> +        return true;
> +    }
> +
> +    wg = blkconf_zone_write_granularity(conf);
> +
> +    if (!QEMU_IS_ALIGNED(bs->bl.zone_size, wg)) {
> +        error_setg(errp, "zone size %" PRIu64 " is not a multiple of the 
> zone "
> +                   "write granularity %" PRIu32, bs->bl.zone_size, wg);
> +        return false;
> +    }
> +
> +    /*
> +     * A write pointer that is not a multiple of the write granularity does 
> not
> +     * fall on a logical block boundary, so the guest can neither read nor 
> write
> +     * at it and the zone can only be recovered by resetting it. A backend 
> that
> +     * records its write pointers, rather than reading them back from a 
> device,
> +     * can hand us such a pointer when the zones were written while the 
> device
> +     * was configured with a smaller logical block size.
> +     */
> +    for (uint32_t i = 0; i < bs->bl.nr_zones; i++) {
> +        uint64_t wp = bs->wps->wp[i];
> +
> +        if (BDRV_ZT_IS_CONV(wp)) {
> +            continue;
> +        }
> +
> +        if (!QEMU_IS_ALIGNED(wp, wg)) {
> +            error_setg(errp, "write pointer 0x%" PRIx64 " of zone %" PRIu32
> +                       " is not a multiple of the zone write granularity %"
> +                       PRIu32, wp, i, wg);
> +            error_append_hint(errp, "The zones were written with a smaller "
> +                              "logical_block_size. Reset them, or keep using 
> "
> +                              "the smaller size.\n");
> +            return false;
> +        }
> +    }
> +
> +    return true;
> +}
> +
>  bool blkconf_apply_backend_options(BlockConf *conf, bool readonly,
>                                     bool resizable, Error **errp)
>  {
> diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c
> index 7977f4abe5..61f7b3cdc1 100644
> --- a/hw/block/virtio-blk.c
> +++ b/hw/block/virtio-blk.c
> @@ -1821,6 +1821,10 @@ static void virtio_blk_device_realize(DeviceState 
> *dev, Error **errp)
>          return;
>      }
>  
> +    if (!blkconf_zoned(&conf->conf, errp)) {
> +        return;
> +    }
> +
>      bs = blk_bs(conf->conf.blk);
>      if (bs->bl.zoned != BLK_Z_NONE) {
>          virtio_add_feature(&s->host_features, VIRTIO_BLK_F_ZONED);
> diff --git a/include/hw/block/block.h b/include/hw/block/block.h
> index f98525c01a..3c96de3222 100644
> --- a/include/hw/block/block.h
> +++ b/include/hw/block/block.h
> @@ -122,6 +122,7 @@ bool blkconf_blocksizes(BlockConf *conf, Error **errp);
>   * value to its guest and validate requests against it.
>   */
>  uint32_t blkconf_zone_write_granularity(BlockConf *conf);
> +bool blkconf_zoned(BlockConf *conf, Error **errp);
>  bool blkconf_apply_backend_options(BlockConf *conf, bool readonly,
>                                     bool resizable, Error **errp);
>  


-- 
Damien Le Moal
Western Digital Research

Reply via email to