qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/4] Add full zoned storage emulation to qcow2 driver
@ 2023-08-28 15:09 Sam Li
  2023-08-28 15:09 ` [PATCH v3 1/4] docs/qcow2: add the zoned format feature Sam Li
                   ` (3 more replies)
  0 siblings, 4 replies; 13+ messages in thread
From: Sam Li @ 2023-08-28 15:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, dmitry.fomichev, Hanna Reitz, Markus Armbruster,
	Eric Blake, hare, qemu-block, stefanha, dlemoal, Sam Li

This patch series add a new extension - zoned format - to the
qcow2 driver thereby allowing full zoned storage emulation on
the qcow2 img file. Users can attach such a qcow2 file to the
guest as a zoned device.

To create a qcow2 file with zoned format, use command like this:
$ qemu-img create -f qcow2 test.qcow2 -o size=768M -o
zone_size=64M -o zone_capacity=64M -o nr_conv_zones=0 -o
max_append_sectors=512 -o max_open_zones=0 -o max_active_zones=0
-o zone_model=1

Then add it to the QEMU command line:
    -blockdev node-name=drive1,driver=qcow2,file.driver=file,file.filename=../qemu/test.qcow2 \
    -device virtio-blk-pci,drive=drive1 \

v2->v3:
- drop zoned_profile option [Klaus]
- reformat doc comments of qcow2 [Markus]
- add input validation and checks for zoned information [Stefan]
- code style: format, comments, documentation, naming [Stefan]
- add tracing function for wp tracking [Stefan]
- reconstruct io path in check_zone_resources [Stefan]

v1->v2:
- add more tests to qemu-io zoned commands
- make zone append change state to full when wp reaches end
- add documentation to qcow2 zoned extension header
- address review comments (Stefan):
  * fix zoned_mata allocation size
  * use bitwise or than addition
  * fix wp index overflow and locking
  * cleanups: comments, naming

Sam Li (4):
  docs/qcow2: add the zoned format feature
  qcow2: add configurations for zoned format extension
  qcow2: add zoned emulation capability
  iotests: test the zoned format feature for qcow2 file

 block/qcow2.c                            | 831 ++++++++++++++++++++++-
 block/qcow2.h                            |  22 +
 block/trace-events                       |   1 +
 docs/interop/qcow2.txt                   |  42 ++
 docs/system/qemu-block-drivers.rst.inc   |  39 ++
 include/block/block_int-common.h         |  13 +
 qapi/block-core.json                     |  30 +-
 tests/qemu-iotests/tests/zoned-qcow2     | 135 ++++
 tests/qemu-iotests/tests/zoned-qcow2.out | 140 ++++
 9 files changed, 1250 insertions(+), 3 deletions(-)
 create mode 100755 tests/qemu-iotests/tests/zoned-qcow2
 create mode 100644 tests/qemu-iotests/tests/zoned-qcow2.out

-- 
2.40.1



^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v3 1/4] docs/qcow2: add the zoned format feature
  2023-08-28 15:09 [PATCH v3 0/4] Add full zoned storage emulation to qcow2 driver Sam Li
@ 2023-08-28 15:09 ` Sam Li
  2023-09-06 20:26   ` Stefan Hajnoczi
  2023-08-28 15:09 ` [PATCH v3 2/4] qcow2: add configurations for zoned format extension Sam Li
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 13+ messages in thread
From: Sam Li @ 2023-08-28 15:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, dmitry.fomichev, Hanna Reitz, Markus Armbruster,
	Eric Blake, hare, qemu-block, stefanha, dlemoal, Sam Li

Add the specs for the zoned format feature of the qcow2 driver.
The qcow2 file can be taken as zoned device and passed through by
virtio-blk device or NVMe ZNS device to the guest given zoned
information.

Signed-off-by: Sam Li <faithilikerun@gmail.com>
---
 docs/system/qemu-block-drivers.rst.inc | 39 ++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/docs/system/qemu-block-drivers.rst.inc b/docs/system/qemu-block-drivers.rst.inc
index 105cb9679c..640ab151a7 100644
--- a/docs/system/qemu-block-drivers.rst.inc
+++ b/docs/system/qemu-block-drivers.rst.inc
@@ -172,6 +172,45 @@ This section describes each format and the options that are supported for it.
     filename`` to check if the NOCOW flag is set or not (Capital 'C' is
     NOCOW flag).
 
+  .. option:: zoned
+    The zoned interface of zoned storage divices can different forms which
+    is referred to as models. This option uses number to represent, 1 for
+    host-managed and 0 for non-zoned.
+
+  .. option:: zone_size
+
+    The size of a zone of the zoned device in bytes. The device is divided
+    into zones of this size with the exception of the last zone, which may
+    be smaller.
+
+  .. option:: zone_capacity
+
+    The initial capacity value for all zones. The capacity must be less than
+    or equal to zone size. If the last zone is smaller, then its capacity is
+    capped. The device follows the ZBC protocol tends to have the same size
+    as its zone.
+
+    The zone capacity is per zone and may be different between zones in real
+    devices. For simplicity, limits QCow2 emulation to the same zone capacity
+    for all zones.
+
+  .. option:: zone_nr_conv
+
+    The number of conventional zones of the zoned device.
+
+  .. option:: max_open_zones
+
+    The maximal allowed open zones.
+
+  .. option:: max_active_zones
+
+    The limit of the zones with implicit open, explicit open or closed state.
+
+  .. option:: max_append_sectors
+
+    The maximal sectors in 512B blocks that is allowed to append to zones
+    while writing.
+
 .. program:: image-formats
 .. option:: qed
 
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v3 2/4] qcow2: add configurations for zoned format extension
  2023-08-28 15:09 [PATCH v3 0/4] Add full zoned storage emulation to qcow2 driver Sam Li
  2023-08-28 15:09 ` [PATCH v3 1/4] docs/qcow2: add the zoned format feature Sam Li
@ 2023-08-28 15:09 ` Sam Li
  2023-09-01 11:07   ` Markus Armbruster
  2023-09-13 20:12   ` Stefan Hajnoczi
  2023-08-28 15:09 ` [PATCH v3 3/4] qcow2: add zoned emulation capability Sam Li
  2023-08-28 15:09 ` [PATCH v3 4/4] iotests: test the zoned format feature for qcow2 file Sam Li
  3 siblings, 2 replies; 13+ messages in thread
From: Sam Li @ 2023-08-28 15:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, dmitry.fomichev, Hanna Reitz, Markus Armbruster,
	Eric Blake, hare, qemu-block, stefanha, dlemoal, Sam Li

To configure the zoned format feature on the qcow2 driver, it
requires following arguments: the device size, zoned profile,
zone model, zone size, zone capacity, number of conventional
zones, limits on zone resources (max append sectors, max open
zones, and max_active_zones).

To create a qcow2 file with zoned format, use command like this:
$ qemu-img create -f qcow2 test.qcow2 -o size=768M -o
zone_size=64M -o zone_capacity=64M -o nr_conv_zones=0 -o
max_append_sectors=512 -o max_open_zones=0 -o max_active_zones=0
-o zone_model=1

Signed-off-by: Sam Li <faithilikerun@gmail.com>
---
 block/qcow2.c                    | 176 ++++++++++++++++++++++++++++++-
 block/qcow2.h                    |  20 ++++
 docs/interop/qcow2.txt           |  36 +++++++
 include/block/block_int-common.h |  13 +++
 qapi/block-core.json             |  30 +++++-
 5 files changed, 273 insertions(+), 2 deletions(-)

diff --git a/block/qcow2.c b/block/qcow2.c
index c51388e99d..7074bfc620 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -73,6 +73,7 @@ typedef struct {
 #define  QCOW2_EXT_MAGIC_CRYPTO_HEADER 0x0537be77
 #define  QCOW2_EXT_MAGIC_BITMAPS 0x23852875
 #define  QCOW2_EXT_MAGIC_DATA_FILE 0x44415441
+#define  QCOW2_EXT_MAGIC_ZONED_FORMAT 0x7a6264
 
 static int coroutine_fn
 qcow2_co_preadv_compressed(BlockDriverState *bs,
@@ -210,6 +211,7 @@ qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
     uint64_t offset;
     int ret;
     Qcow2BitmapHeaderExt bitmaps_ext;
+    Qcow2ZonedHeaderExtension zoned_ext;
 
     if (need_update_header != NULL) {
         *need_update_header = false;
@@ -431,6 +433,55 @@ qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
             break;
         }
 
+        case QCOW2_EXT_MAGIC_ZONED_FORMAT:
+        {
+            if (ext.len != sizeof(zoned_ext)) {
+                error_setg(errp, "zoned_ext: Invalid extension length");
+                return -EINVAL;
+            }
+            ret = bdrv_pread(bs->file, offset, ext.len, &zoned_ext, 0);
+            if (ret < 0) {
+                error_setg_errno(errp, -ret, "zoned_ext: "
+                                             "Could not read ext header");
+                return ret;
+            }
+
+            zoned_ext.zone_size = be32_to_cpu(zoned_ext.zone_size);
+            zoned_ext.zone_capacity = be32_to_cpu(zoned_ext.zone_capacity);
+            zoned_ext.nr_conv_zones = be32_to_cpu(zoned_ext.nr_conv_zones);
+            zoned_ext.nr_zones = be32_to_cpu(zoned_ext.nr_zones);
+            zoned_ext.max_open_zones = be32_to_cpu(zoned_ext.max_open_zones);
+            zoned_ext.max_active_zones =
+                be32_to_cpu(zoned_ext.max_active_zones);
+            zoned_ext.max_append_sectors =
+                be32_to_cpu(zoned_ext.max_append_sectors);
+            s->zoned_header = zoned_ext;
+
+            /* refuse to open broken images */
+            if (zoned_ext.zone_size == 0) {
+                error_setg(errp, "Zoned extension header zone_size field "
+                                 "can not be 0");
+                return -EINVAL;
+            }
+            if (zoned_ext.zone_capacity > zoned_ext.zone_size) {
+                error_setg(errp, "Zoned extension header zone_capacity field "
+                                 "can not be larger that zone_size field");
+                return -EINVAL;
+            }
+            if (zoned_ext.nr_zones != DIV_ROUND_UP(
+                bs->total_sectors * BDRV_SECTOR_SIZE, zoned_ext.zone_size)) {
+                error_setg(errp, "Zoned extension header nr_zones field "
+                                 "gets wrong");
+                return -EINVAL;
+            }
+
+#ifdef DEBUG_EXT
+            printf("Qcow2: Got zoned format extension: "
+                   "offset=%" PRIu32 "\n", offset);
+#endif
+            break;
+        }
+
         default:
             /* unknown magic - save it in case we need to rewrite the header */
             /* If you add a new feature, make sure to also update the fast
@@ -1967,6 +2018,14 @@ static void qcow2_refresh_limits(BlockDriverState *bs, Error **errp)
     }
     bs->bl.pwrite_zeroes_alignment = s->subcluster_size;
     bs->bl.pdiscard_alignment = s->cluster_size;
+    bs->bl.zoned = s->zoned_header.zoned;
+    bs->bl.nr_zones = s->zoned_header.nr_zones;
+    bs->wps = s->wps;
+    bs->bl.max_append_sectors = s->zoned_header.max_append_sectors;
+    bs->bl.max_active_zones = s->zoned_header.max_active_zones;
+    bs->bl.max_open_zones = s->zoned_header.max_open_zones;
+    bs->bl.zone_size = s->zoned_header.zone_size;
+    bs->bl.write_granularity = BDRV_SECTOR_SIZE;
 }
 
 static int qcow2_reopen_prepare(BDRVReopenState *state,
@@ -3089,6 +3148,30 @@ int qcow2_update_header(BlockDriverState *bs)
         buflen -= ret;
     }
 
+    /* Zoned devices header extension */
+    if (s->zoned_header.zoned == BLK_Z_HM) {
+        Qcow2ZonedHeaderExtension zoned_header = {
+            .zoned              = s->zoned_header.zoned,
+            .zone_size          = cpu_to_be32(s->zoned_header.zone_size),
+            .zone_capacity      = cpu_to_be32(s->zoned_header.zone_capacity),
+            .nr_conv_zones      = cpu_to_be32(s->zoned_header.nr_conv_zones),
+            .nr_zones           = cpu_to_be32(s->zoned_header.nr_zones),
+            .max_open_zones     = cpu_to_be32(s->zoned_header.max_open_zones),
+            .max_active_zones   =
+                cpu_to_be32(s->zoned_header.max_active_zones),
+            .max_append_sectors =
+                cpu_to_be32(s->zoned_header.max_append_sectors)
+        };
+        ret = header_ext_add(buf, QCOW2_EXT_MAGIC_ZONED_FORMAT,
+                             &zoned_header, sizeof(zoned_header),
+                             buflen);
+        if (ret < 0) {
+            goto fail;
+        }
+        buf += ret;
+        buflen -= ret;
+    }
+
     /* Keep unknown header extensions */
     QLIST_FOREACH(uext, &s->unknown_header_ext, next) {
         ret = header_ext_add(buf, uext->magic, uext->data, uext->len, buflen);
@@ -3768,11 +3851,60 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
     }
 
     /* Set the external data file if necessary */
+    BDRVQcow2State *s = blk_bs(blk)->opaque;
     if (data_bs) {
-        BDRVQcow2State *s = blk_bs(blk)->opaque;
         s->image_data_file = g_strdup(data_bs->filename);
     }
 
+    if (qcow2_opts->has_zone_model && qcow2_opts->zone_model == BLK_Z_HM) {
+        if (qcow2_opts->has_zone_size && qcow2_opts->zone_size == 0) {
+            s->zoned_header.zoned = BLK_Z_NONE;
+            error_setg(errp, "Zoned devices can not allow a larger-than-zero "
+                             "zone_size");
+            goto out;
+        }
+        s->zoned_header.zoned = qcow2_opts->zone_model;
+        s->zoned_header.zone_size = qcow2_opts->zone_size;
+        s->zoned_header.nr_zones = DIV_ROUND_UP(qcow2_opts->size,
+                                                qcow2_opts->zone_size);
+
+        if (qcow2_opts->has_zone_capacity) {
+            if (qcow2_opts->zone_capacity > qcow2_opts->zone_size) {
+                s->zoned_header.zoned = BLK_Z_NONE;
+                error_setg(errp, "zone capacity %" PRIu64 "B exceeds zone size "
+                           "%" PRIu64"B", qcow2_opts->zone_capacity,
+                           qcow2_opts->zone_size);
+                goto out;
+            }
+            s->zoned_header.zone_capacity = qcow2_opts->zone_capacity;
+        } else {
+            s->zoned_header.zone_capacity = qcow2_opts->zone_size;
+        }
+
+        if (qcow2_opts->has_nr_conv_zones) {
+            s->zoned_header.nr_conv_zones = qcow2_opts->nr_conv_zones;
+        }
+
+        if (qcow2_opts->has_max_active_zones) {
+            if (qcow2_opts->max_open_zones > qcow2_opts->max_active_zones) {
+                s->zoned_header.zoned = BLK_Z_NONE;
+                error_setg(errp, "max_open_zones %" PRIu32 " exceeds "
+                           "max_active_zones %" PRIu32"",
+                           qcow2_opts->max_open_zones,
+                           qcow2_opts->max_active_zones);
+                goto out;
+            }
+            if (qcow2_opts->has_max_open_zones) {
+                s->zoned_header.max_open_zones = qcow2_opts->max_active_zones;
+            } else {
+                s->zoned_header.max_open_zones = qcow2_opts->max_active_zones;
+            }
+        }
+        s->zoned_header.max_append_sectors = qcow2_opts->max_append_sectors;
+    } else {
+        s->zoned_header.zoned = BLK_Z_NONE;
+    }
+
     /* Create a full header (including things like feature table) */
     ret = qcow2_update_header(blk_bs(blk));
     bdrv_graph_co_rdunlock();
@@ -3903,6 +4035,13 @@ qcow2_co_create_opts(BlockDriver *drv, const char *filename, QemuOpts *opts,
         { BLOCK_OPT_COMPAT_LEVEL,       "version" },
         { BLOCK_OPT_DATA_FILE_RAW,      "data-file-raw" },
         { BLOCK_OPT_COMPRESSION_TYPE,   "compression-type" },
+        { BLOCK_OPT_Z_MODEL,            "zone-model"},
+        { BLOCK_OPT_Z_NR_COV,           "nr-conv-zones"},
+        { BLOCK_OPT_Z_MOZ,              "max-open-zones"},
+        { BLOCK_OPT_Z_MAZ,              "max-active-zones"},
+        { BLOCK_OPT_Z_MAS,              "max-append-sectors"},
+        { BLOCK_OPT_Z_SIZE,             "zone-size"},
+        { BLOCK_OPT_Z_CAP,              "zone-capacity"},
         { NULL, NULL },
     };
 
@@ -6066,6 +6205,41 @@ static QemuOptsList qcow2_create_opts = {
             .help = "Compression method used for image cluster "        \
                     "compression",                                      \
             .def_value_str = "zlib"                                     \
+        },                                                              \
+        {                                                               \
+            .name = BLOCK_OPT_Z_MODEL,                                  \
+            .type = QEMU_OPT_NUMBER,                                    \
+            .help = "zone model",                                      \
+        },                                                              \
+        {                                                               \
+            .name = BLOCK_OPT_Z_SIZE,                                   \
+            .type = QEMU_OPT_SIZE,                                      \
+            .help = "zone size",                                        \
+        },                                                              \
+        {                                                               \
+            .name = BLOCK_OPT_Z_CAP,                                    \
+            .type = QEMU_OPT_SIZE,                                      \
+            .help = "zone capacity",                                    \
+        },                                                              \
+        {                                                               \
+            .name = BLOCK_OPT_Z_NR_COV,                                 \
+            .type = QEMU_OPT_NUMBER,                                    \
+            .help = "numbers of conventional zones",                    \
+        },                                                              \
+        {                                                               \
+            .name = BLOCK_OPT_Z_MAS,                                    \
+            .type = QEMU_OPT_NUMBER,                                    \
+            .help = "max append sectors",                               \
+        },                                                              \
+        {                                                               \
+            .name = BLOCK_OPT_Z_MAZ,                                    \
+            .type = QEMU_OPT_NUMBER,                                    \
+            .help = "max active zones",                                 \
+        },                                                              \
+        {                                                               \
+            .name = BLOCK_OPT_Z_MOZ,                                    \
+            .type = QEMU_OPT_NUMBER,                                    \
+            .help = "max open zones",                                   \
         },
         QCOW_COMMON_OPTIONS,
         { /* end of list */ }
diff --git a/block/qcow2.h b/block/qcow2.h
index f789ce3ae0..edb8eebcb3 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -236,6 +236,19 @@ typedef struct Qcow2CryptoHeaderExtension {
     uint64_t length;
 } QEMU_PACKED Qcow2CryptoHeaderExtension;
 
+typedef struct Qcow2ZonedHeaderExtension {
+    /* Zoned device attributes */
+    uint8_t zoned;
+    uint8_t reserved[3];
+    uint32_t zone_size;
+    uint32_t zone_capacity;
+    uint32_t nr_conv_zones;
+    uint32_t nr_zones;
+    uint32_t max_active_zones;
+    uint32_t max_open_zones;
+    uint32_t max_append_sectors;
+} QEMU_PACKED Qcow2ZonedHeaderExtension;
+
 typedef struct Qcow2UnknownHeaderExtension {
     uint32_t magic;
     uint32_t len;
@@ -422,6 +435,13 @@ typedef struct BDRVQcow2State {
      * is to convert the image with the desired compression type set.
      */
     Qcow2CompressionType compression_type;
+
+    /* States of zoned device */
+    Qcow2ZonedHeaderExtension zoned_header;
+    uint32_t nr_zones_exp_open;
+    uint32_t nr_zones_imp_open;
+    uint32_t nr_zones_closed;
+    BlockZoneWps *wps;
 } BDRVQcow2State;
 
 typedef struct Qcow2COWRegion {
diff --git a/docs/interop/qcow2.txt b/docs/interop/qcow2.txt
index 2c4618375a..80ff03a826 100644
--- a/docs/interop/qcow2.txt
+++ b/docs/interop/qcow2.txt
@@ -331,6 +331,42 @@ The fields of the bitmaps extension are:
                    Offset into the image file at which the bitmap directory
                    starts. Must be aligned to a cluster boundary.
 
+== Zoned extension ==
+
+The zoned extension is an optional header extension. It contains fields for
+emulating the zoned stroage model (https://zonedstorage.io/).
+
+The fields of the zoned extension are:
+    Byte        0:  zoned
+                    Zoned model, 1 for host-managed and 0 for non-zoned devices.
+
+            1 - 3:  Reserved, must be zero.
+
+            4 - 7:  zone_size
+                    Total number of logical blocks within the zones in bytes.
+
+           8 - 11:  zone_capacity
+                    The number of writable logical blocks within the zones in
+                    bytes. A zone capacity is always smaller or equal to the
+                    zone size.
+
+          12 - 15:  nr_conv_zones
+                    The number of conventional zones.
+
+          16 - 19:  nr_zones
+                    The number of zones.
+
+          20 - 23:  max_active_zones
+                    The limit of the zones that have the implicit open,
+                    explicit open or closed state.
+
+          24 - 27:  max_open_zones
+                    The maximal allowed open zones.
+
+          28 - 35:  max_append_sectors
+                    The maximal data size in sectors of a zone
+                    append request that can be issued to the device.
+
 == Full disk encryption header pointer ==
 
 The full disk encryption header must be present if, and only if, the
diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h
index 74195c3004..332c0c765c 100644
--- a/include/block/block_int-common.h
+++ b/include/block/block_int-common.h
@@ -57,6 +57,13 @@
 #define BLOCK_OPT_DATA_FILE_RAW     "data_file_raw"
 #define BLOCK_OPT_COMPRESSION_TYPE  "compression_type"
 #define BLOCK_OPT_EXTL2             "extended_l2"
+#define BLOCK_OPT_Z_MODEL           "zone_model"
+#define BLOCK_OPT_Z_SIZE            "zone_size"
+#define BLOCK_OPT_Z_CAP             "zone_capacity"
+#define BLOCK_OPT_Z_NR_COV          "nr_conv_zones"
+#define BLOCK_OPT_Z_MAS             "max_append_sectors"
+#define BLOCK_OPT_Z_MAZ             "max_active_zones"
+#define BLOCK_OPT_Z_MOZ             "max_open_zones"
 
 #define BLOCK_PROBE_BUF_SIZE        512
 
@@ -878,6 +885,12 @@ typedef struct BlockLimits {
     /* zone size expressed in bytes */
     uint32_t zone_size;
 
+    /*
+     * the number of usable logical blocks within the zone, expressed
+     * in bytes. A zone capacity is smaller or equal to the zone size.
+     */
+    uint32_t zone_capacity;
+
     /* total number of zones */
     uint32_t nr_zones;
 
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 2b1d493d6e..0d8f9e0a88 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -5021,6 +5021,27 @@
 # @compression-type: The image cluster compression method
 #     (default: zlib, since 5.1)
 #
+# @zone-model: Zoned device model, 1 for host-managed and 0 for
+#     non-zoned devices (default: 0, since 8.0)
+#
+# @zone-size: Total number of logical blocks within zones in bytes
+#     (since 8.0)
+#
+# @zone-capacity: The number of usable logical blocks within zones
+#     in bytes. A zone capacity is always smaller or equal to the
+#     zone size. (since 8.0)
+#
+# @nr-conv-zones: The number of conventional zones of the zoned device
+#     (since 8.0)
+#
+# @max-open-zones: The maximal allowed open zones (since 8.0)
+#
+# @max-active-zones: The limit of the zones that have the implicit
+#     open, explicit open or closed state (since 8.0)
+#
+# @max-append-sectors: The maximal data size in sectors of a zone
+#     append request that can be issued to the device. (since 8.0)
+#
 # Since: 2.12
 ##
 { 'struct': 'BlockdevCreateOptionsQcow2',
@@ -5037,7 +5058,14 @@
             '*preallocation':   'PreallocMode',
             '*lazy-refcounts':  'bool',
             '*refcount-bits':   'int',
-            '*compression-type':'Qcow2CompressionType' } }
+            '*compression-type':'Qcow2CompressionType',
+            '*zone-model':         'uint8',
+            '*zone-size':          'size',
+            '*zone-capacity':      'size',
+            '*nr-conv-zones':      'uint32',
+            '*max-open-zones':     'uint32',
+            '*max-active-zones':   'uint32',
+            '*max-append-sectors': 'uint32' } }
 
 ##
 # @BlockdevCreateOptionsQed:
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v3 3/4] qcow2: add zoned emulation capability
  2023-08-28 15:09 [PATCH v3 0/4] Add full zoned storage emulation to qcow2 driver Sam Li
  2023-08-28 15:09 ` [PATCH v3 1/4] docs/qcow2: add the zoned format feature Sam Li
  2023-08-28 15:09 ` [PATCH v3 2/4] qcow2: add configurations for zoned format extension Sam Li
@ 2023-08-28 15:09 ` Sam Li
  2023-09-13 21:11   ` Stefan Hajnoczi
  2023-08-28 15:09 ` [PATCH v3 4/4] iotests: test the zoned format feature for qcow2 file Sam Li
  3 siblings, 1 reply; 13+ messages in thread
From: Sam Li @ 2023-08-28 15:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, dmitry.fomichev, Hanna Reitz, Markus Armbruster,
	Eric Blake, hare, qemu-block, stefanha, dlemoal, Sam Li

By adding zone operations and zoned metadata, the zoned emulation
capability enables full emulation support of zoned device using
a qcow2 file. The zoned device metadata includes zone type,
zoned device state and write pointer of each zone, which is stored
to an array of unsigned integers.

Each zone of a zoned device makes state transitions following
the zone state machine. The zone state machine mainly describes
five states, IMPLICIT OPEN, EXPLICIT OPEN, FULL, EMPTY and CLOSED.
READ ONLY and OFFLINE states will generally be affected by device
internal events. The operations on zones cause corresponding state
changing.

Zoned devices have a limit on zone resources, which puts constraints on
write operations into zones.

Signed-off-by: Sam Li <faithilikerun@gmail.com>
---
 block/qcow2.c          | 657 ++++++++++++++++++++++++++++++++++++++++-
 block/qcow2.h          |   2 +
 block/trace-events     |   1 +
 docs/interop/qcow2.txt |   6 +
 4 files changed, 664 insertions(+), 2 deletions(-)

diff --git a/block/qcow2.c b/block/qcow2.c
index 7074bfc620..bc98d98c8e 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -194,6 +194,153 @@ qcow2_extract_crypto_opts(QemuOpts *opts, const char *fmt, Error **errp)
     return cryptoopts_qdict;
 }
 
+#define QCOW2_ZT_IS_CONV(wp)    (wp & 1ULL << 59)
+
+static inline int qcow2_get_wp(uint64_t wp)
+{
+    /* clear state and type information */
+    return ((wp << 5) >> 5);
+}
+
+static inline int qcow2_get_zs(uint64_t wp)
+{
+    return (wp >> 60);
+}
+
+static inline void qcow2_set_zs(uint64_t *wp, BlockZoneState zs)
+{
+    uint64_t addr = qcow2_get_wp(*wp);
+    addr |= ((uint64_t)zs << 60);
+    *wp = addr;
+}
+
+/*
+ * Perform a state assignment and a flush operation that writes the new wp
+ * value to the dedicated location of the disk file.
+ */
+static int qcow2_write_wp_at(BlockDriverState *bs, uint64_t *wp,
+                             uint32_t index, BlockZoneState zs) {
+    BDRVQcow2State *s = bs->opaque;
+    uint64_t wpv = *wp;
+    int ret;
+
+    qcow2_set_zs(wp, zs);
+    ret = bdrv_pwrite(bs->file, s->zoned_header.zonedmeta_offset
+        + sizeof(uint64_t) * index, sizeof(uint64_t), wp, 0);
+
+    if (ret < 0) {
+        goto exit;
+    }
+    trace_qcow2_wp_tracking(index, qcow2_get_wp(*wp) >> BDRV_SECTOR_BITS);
+    return ret;
+
+exit:
+    *wp = wpv;
+    error_report("Failed to write metadata with file");
+    return ret;
+}
+
+static bool qcow2_check_active_zones(BlockDriverState *bs)
+{
+    BDRVQcow2State *s = bs->opaque;
+
+    if (!s->zoned_header.max_active_zones) {
+        return true;
+    }
+
+    if (s->nr_zones_exp_open + s->nr_zones_imp_open + s->nr_zones_closed
+        < s->zoned_header.max_active_zones) {
+        return true;
+    }
+
+    return false;
+}
+
+static int qcow2_check_open_zones(BlockDriverState *bs)
+{
+    BDRVQcow2State *s = bs->opaque;
+    int ret;
+
+    if (!s->zoned_header.max_open_zones) {
+        return 0;
+    }
+
+    if (s->nr_zones_exp_open + s->nr_zones_imp_open
+        < s->zoned_header.max_open_zones) {
+        return 0;
+    }
+
+    if(s->nr_zones_imp_open && qcow2_check_active_zones(bs)) {
+        /* TODO: it takes O(n) time complexity (n = nr_zones).
+         * Optimizations required. */
+        /* close one implicitly open zones to make it available */
+        for (int i = s->zoned_header.nr_conv_zones;
+        i < bs->bl.nr_zones; ++i) {
+            uint64_t *wp = &bs->wps->wp[i];
+            if (qcow2_get_zs(*wp) == BLK_ZS_IOPEN) {
+                ret = qcow2_write_wp_at(bs, wp, i, BLK_ZS_CLOSED);
+                if (ret < 0) {
+                    return ret;
+                }
+                bs->wps->wp[i] = *wp;
+                s->nr_zones_imp_open--;
+                s->nr_zones_closed++;
+                break;
+            }
+        }
+        return 0;
+    }
+
+    return -EINVAL;
+}
+
+/*
+ * The zoned device has limited zone resources of open, closed, active
+ * zones.
+ */
+static int qcow2_check_zone_resources(BlockDriverState *bs,
+                                      BlockZoneState zs)
+{
+    int ret = 0;
+
+    switch (zs) {
+    case BLK_ZS_EMPTY:
+        if (!qcow2_check_active_zones(bs)) {
+            error_report("No enough active zones");
+            return -EINVAL;
+        }
+        return ret;
+    case BLK_ZS_CLOSED:
+        ret = qcow2_check_open_zones(bs);
+        if (ret < 0) {
+            error_report("No enough open zones");
+            return ret;
+        }
+        return ret;
+    default:
+        return -EINVAL;
+    }
+
+}
+
+static inline int qcow2_refresh_zonedmeta(BlockDriverState *bs)
+{
+    int ret;
+    BDRVQcow2State *s = bs->opaque;
+    uint64_t wps_size = s->zoned_header.zonedmeta_size;
+    g_autofree uint64_t *temp = NULL;
+    temp = g_new(uint64_t, wps_size);
+    ret = bdrv_pread(bs->file, s->zoned_header.zonedmeta_offset,
+                     wps_size, temp, 0);
+    if (ret < 0) {
+        error_report("Can not read metadata");
+        return ret;
+    }
+
+    memcpy(s->wps->wp, temp, wps_size);
+    return 0;
+}
+
 /*
  * read qcow2 extension and fill bs
  * start reading from start_offset
@@ -455,7 +602,19 @@ qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
                 be32_to_cpu(zoned_ext.max_active_zones);
             zoned_ext.max_append_sectors =
                 be32_to_cpu(zoned_ext.max_append_sectors);
+            zoned_ext.zonedmeta_offset =
+                be64_to_cpu(zoned_ext.zonedmeta_offset);
+            zoned_ext.zonedmeta_size = be64_to_cpu(zoned_ext.zonedmeta_size);
             s->zoned_header = zoned_ext;
+            s->wps = g_malloc(sizeof(BlockZoneWps)
+                + s->zoned_header.zonedmeta_size);
+            ret = qcow2_refresh_zonedmeta(bs);
+            if (ret < 0) {
+                error_setg_errno(errp, -ret, "zonedmeta: "
+                                             "Could not update zoned meta");
+                return ret;
+            }
+            qemu_co_mutex_init(&s->wps->colock);
 
             /* refuse to open broken images */
             if (zoned_ext.zone_size == 0) {
@@ -2716,9 +2875,26 @@ qcow2_co_pwritev_part(BlockDriverState *bs, int64_t offset, int64_t bytes,
     uint64_t host_offset;
     QCowL2Meta *l2meta = NULL;
     AioTaskPool *aio = NULL;
+    int64_t start_offset, start_bytes;
+    BlockZoneState zs;
+    int64_t end;
+    uint64_t *wp;
+    int64_t zone_size = bs->bl.zone_size;
+    int index;
 
     trace_qcow2_writev_start_req(qemu_coroutine_self(), offset, bytes);
 
+    start_offset = offset;
+    start_bytes = bytes;
+    /* The offset should not less than the wp of that
+     * zone where offset starts.  */
+    if (zone_size) {
+        index = start_offset / zone_size;
+        wp = &bs->wps->wp[index];
+        if (offset < qcow2_get_wp(*wp)) {
+            return -EINVAL;
+        }
+    }
     while (bytes != 0 && aio_task_pool_status(aio) == 0) {
 
         l2meta = NULL;
@@ -2764,6 +2940,47 @@ qcow2_co_pwritev_part(BlockDriverState *bs, int64_t offset, int64_t bytes,
         qiov_offset += cur_bytes;
         trace_qcow2_writev_done_part(qemu_coroutine_self(), cur_bytes);
     }
+
+    if (zone_size) {
+        index = start_offset / zone_size;
+        wp = &bs->wps->wp[index];
+        uint64_t wpv = *wp;
+        if (!QCOW2_ZT_IS_CONV(wpv)) {
+            /*
+             * Implicitly open one closed zone to write if there are zone resources
+             * left.
+             */
+            zs = qcow2_get_zs(wpv);
+            if (zs == BLK_ZS_CLOSED || zs == BLK_ZS_EMPTY) {
+                ret = qcow2_check_zone_resources(bs, zs);
+                if (ret < 0) {
+                    goto fail_nometa;
+                }
+
+                if (zs == BLK_ZS_CLOSED) {
+                    s->nr_zones_closed--;
+                    s->nr_zones_imp_open++;
+                } else {
+                    s->nr_zones_imp_open++;
+                }
+            }
+
+            /* align up (start_offset, zone_size), the start offset is not
+             * necessarily power of two. */
+            end = ((start_offset + zone_size) / zone_size) * zone_size;
+            if (start_offset + start_bytes <= end) {
+                *wp = start_offset + start_bytes;
+            } else {
+                ret = -EINVAL;
+                goto fail_nometa;
+            }
+
+            ret = qcow2_write_wp_at(bs, wp, index,BLK_ZS_IOPEN);
+            if (ret < 0) {
+                goto fail_nometa;
+            }
+        }
+    }
     ret = 0;
 
     qemu_co_mutex_lock(&s->lock);
@@ -3160,7 +3377,9 @@ int qcow2_update_header(BlockDriverState *bs)
             .max_active_zones   =
                 cpu_to_be32(s->zoned_header.max_active_zones),
             .max_append_sectors =
-                cpu_to_be32(s->zoned_header.max_append_sectors)
+                cpu_to_be32(s->zoned_header.max_append_sectors),
+            .zonedmeta_offset   = cpu_to_be64(s->zoned_header.zonedmeta_offset),
+            .zonedmeta_size     = cpu_to_be64(s->zoned_header.zonedmeta_size)
         };
         ret = header_ext_add(buf, QCOW2_EXT_MAGIC_ZONED_FORMAT,
                              &zoned_header, sizeof(zoned_header),
@@ -3565,7 +3784,8 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
     int version;
     int refcount_order;
     uint64_t *refcount_table;
-    int ret;
+    uint64_t zoned_meta_size, zoned_clusterlen;
+    int ret, offset, i;
     uint8_t compression_type = QCOW2_COMPRESSION_TYPE_ZLIB;
 
     assert(create_options->driver == BLOCKDEV_DRIVER_QCOW2);
@@ -3901,6 +4121,49 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
             }
         }
         s->zoned_header.max_append_sectors = qcow2_opts->max_append_sectors;
+
+        uint32_t nrz = s->zoned_header.nr_zones;
+        zoned_meta_size =  sizeof(uint64_t) * nrz;
+        g_autofree uint64_t *meta = NULL;
+        meta = g_new0(uint64_t, nrz);
+
+        for (i = 0; i < s->zoned_header.nr_conv_zones; ++i) {
+            meta[i] = i * s->zoned_header.zone_size;
+            meta[i] |= 1ULL << 59;
+        }
+
+        for (; i < nrz; ++i) {
+            meta[i] = i * s->zoned_header.zone_size;
+            /* For sequential zones, the first four most significant bit
+             * indicates zone states. */
+            meta[i] |= ((uint64_t)BLK_ZS_EMPTY << 60);
+        }
+
+        offset = qcow2_alloc_clusters(blk_bs(blk), zoned_meta_size);
+        if (offset < 0) {
+            error_setg_errno(errp, -offset, "Could not allocate clusters "
+                                            "for zoned metadata size");
+            goto out;
+        }
+        s->zoned_header.zonedmeta_offset = offset;
+        s->zoned_header.zonedmeta_size = zoned_meta_size;
+
+        zoned_clusterlen = size_to_clusters(s, zoned_meta_size)
+                * s->cluster_size;
+        assert(qcow2_pre_write_overlap_check(bs, 0, offset,
+                                             zoned_clusterlen,false) == 0);
+        ret = bdrv_pwrite_zeroes(blk_bs(blk)->file, offset,
+                                 zoned_clusterlen, 0);
+        if (ret < 0) {
+            error_setg_errno(errp, -ret, "Could not zero fill zoned metadata");
+            goto out;
+        }
+        ret = bdrv_pwrite(blk_bs(blk)->file, offset, zoned_meta_size, meta, 0);
+        if (ret < 0) {
+            error_setg_errno(errp, -ret, "Could not write zoned metadata "
+                                         "to disk");
+            goto out;
+        }
     } else {
         s->zoned_header.zoned = BLK_Z_NONE;
     }
@@ -4239,6 +4502,392 @@ static coroutine_fn int qcow2_co_pdiscard(BlockDriverState *bs,
     return ret;
 }
 
+static int coroutine_fn
+qcow2_co_zone_report(BlockDriverState *bs, int64_t offset,
+                     unsigned int *nr_zones, BlockZoneDescriptor *zones)
+{
+    BDRVQcow2State *s = bs->opaque;
+    uint64_t zone_size = s->zoned_header.zone_size;
+    int64_t capacity = bs->total_sectors << BDRV_SECTOR_BITS;
+    int64_t size = bs->bl.nr_zones * zone_size;
+    unsigned int nrz = *nr_zones;
+    int i = 0;
+    int si;
+
+    if (offset >= capacity) {
+        error_report("offset %" PRId64 " is equal to or greater than the"
+                     "device capacity %" PRId64 "", offset, capacity);
+        return -EINVAL;
+    }
+
+    if (nrz > bs->bl.nr_zones) {
+        error_report("nr_zones %" PRId32 " should not exceed the device zones"
+                     "%" PRId32 "", nrz, bs->bl.nr_zones);
+        return -EINVAL;
+    }
+
+    if (zone_size > 0) {
+        si = offset / zone_size;
+        qemu_co_mutex_lock(&bs->wps->colock);
+        for (; i < nrz; ++i) {
+            if (i + si >= bs->bl.nr_zones) {
+                break;
+            }
+
+            zones[i].start = (si + i) * zone_size;
+
+            /* The last zone can be smaller than the zone size */
+            if ((si + i + 1) == bs->bl.nr_zones && size > capacity) {
+                uint32_t l = zone_size - (size - capacity);
+                zones[i].length = l;
+                zones[i].cap = l;
+            } else {
+                zones[i].length = zone_size;
+                zones[i].cap = zone_size;
+            }
+
+            uint64_t wp = bs->wps->wp[si + i];
+            if (QCOW2_ZT_IS_CONV(wp)) {
+                zones[i].type = BLK_ZT_CONV;
+                zones[i].state = BLK_ZS_NOT_WP;
+                /* Clear the zone type bit */
+                wp &= ~(1ULL << 59);
+            } else {
+                zones[i].type = BLK_ZT_SWR;
+                zones[i].state = qcow2_get_zs(wp);
+                /* Clear the zone state bits */
+                wp = qcow2_get_wp(wp);
+            }
+            zones[i].wp = wp;
+        }
+        qemu_co_mutex_unlock(&bs->wps->colock);
+    }
+    *nr_zones = i;
+    return 0;
+}
+
+static int qcow2_open_zone(BlockDriverState *bs, uint32_t index) {
+    BDRVQcow2State *s = bs->opaque;
+    int ret;
+
+    qemu_co_mutex_lock(&bs->wps->colock);
+    uint64_t *wp = &bs->wps->wp[index];
+    BlockZoneState zs = qcow2_get_zs(*wp);
+
+    switch(zs) {
+    case BLK_ZS_EMPTY:
+        ret = qcow2_check_zone_resources(bs, BLK_ZS_EMPTY);
+        if (ret < 0) {
+            goto unlock;
+        }
+        break;
+    case BLK_ZS_IOPEN:
+        s->nr_zones_imp_open--;
+        break;
+    case BLK_ZS_EOPEN:
+        return 0;
+    case BLK_ZS_CLOSED:
+        ret = qcow2_check_zone_resources(bs, BLK_ZS_CLOSED);
+        if (ret < 0) {
+            goto unlock;
+        }
+        s->nr_zones_closed--;
+        break;
+    case BLK_ZS_FULL:
+        break;
+    default:
+        ret = -EINVAL;
+        goto unlock;
+    }
+    ret = qcow2_write_wp_at(bs, wp, index, BLK_ZS_EOPEN);
+    if (!ret) {
+        s->nr_zones_exp_open++;
+    }
+
+unlock:
+    qemu_co_mutex_unlock(&bs->wps->colock);
+    return ret;
+}
+
+static int qcow2_close_zone(BlockDriverState *bs, uint32_t index) {
+    BDRVQcow2State *s = bs->opaque;
+    int ret;
+
+    qemu_co_mutex_lock(&bs->wps->colock);
+    uint64_t *wp = &bs->wps->wp[index];
+    BlockZoneState zs = qcow2_get_zs(*wp);
+
+    switch(zs) {
+    case BLK_ZS_EMPTY:
+        break;
+    case BLK_ZS_IOPEN:
+        s->nr_zones_imp_open--;
+        break;
+    case BLK_ZS_EOPEN:
+        s->nr_zones_exp_open--;
+        break;
+    case BLK_ZS_CLOSED:
+        ret = qcow2_check_zone_resources(bs, BLK_ZS_CLOSED);
+        if (ret < 0) {
+            goto unlock;
+        }
+        s->nr_zones_closed--;
+        break;
+    case BLK_ZS_FULL:
+        break;
+    default:
+        ret = -EINVAL;
+        goto unlock;
+    }
+
+    if (zs == BLK_ZS_EMPTY) {
+        ret = qcow2_write_wp_at(bs, wp, index, BLK_ZS_EMPTY);
+    } else {
+        ret = qcow2_write_wp_at(bs, wp, index, BLK_ZS_CLOSED);
+        if (!ret) {
+            s->nr_zones_closed++;
+        }
+    }
+
+unlock:
+    qemu_co_mutex_unlock(&bs->wps->colock);
+    return ret;
+}
+
+static int qcow2_finish_zone(BlockDriverState *bs, uint32_t index) {
+    BDRVQcow2State *s = bs->opaque;
+    int ret;
+
+    qemu_co_mutex_lock(&bs->wps->colock);
+    uint64_t *wp = &bs->wps->wp[index];
+    BlockZoneState zs = qcow2_get_zs(*wp);
+
+    switch(zs) {
+    case BLK_ZS_EMPTY:
+        ret = qcow2_check_zone_resources(bs, BLK_ZS_EMPTY);
+        if (ret < 0) {
+            goto unlock;
+        }
+        break;
+    case BLK_ZS_IOPEN:
+        s->nr_zones_imp_open--;
+        break;
+    case BLK_ZS_EOPEN:
+        s->nr_zones_exp_open--;
+        break;
+    case BLK_ZS_CLOSED:
+        ret = qcow2_check_zone_resources(bs, BLK_ZS_CLOSED);
+        if (ret < 0) {
+            goto unlock;
+        }
+        s->nr_zones_closed--;
+        break;
+    case BLK_ZS_FULL:
+        ret = 0;
+        goto unlock;
+    default:
+        ret = -EINVAL;
+        goto unlock;
+    }
+
+    *wp = ((uint64_t)index + 1) * s->zoned_header.zone_size;
+    ret = qcow2_write_wp_at(bs, wp, index, BLK_ZS_FULL);
+
+unlock:
+    qemu_co_mutex_unlock(&bs->wps->colock);
+    return ret;
+}
+
+static int qcow2_reset_zone(BlockDriverState *bs, uint32_t index,
+                            int64_t len) {
+    BDRVQcow2State *s = bs->opaque;
+    int nrz = bs->bl.nr_zones;
+    int zone_size = bs->bl.zone_size;
+    int n, ret = 0;
+
+    qemu_co_mutex_lock(&bs->wps->colock);
+    uint64_t *wp = &bs->wps->wp[index];
+    if (len == bs->total_sectors << BDRV_SECTOR_BITS) {
+        n = nrz;
+        index = 0;
+    } else {
+        n = len / zone_size;
+    }
+
+    for (int i = 0; i < n; ++i) {
+        uint64_t *wp_i = (uint64_t *)(wp + i);
+        uint64_t wpi_v = *wp_i;
+        if (QCOW2_ZT_IS_CONV(wpi_v)) {
+            continue;
+        }
+        
+        BlockZoneState zs = qcow2_get_zs(wpi_v);
+        switch (zs) {
+        case BLK_ZS_EMPTY:
+            break;
+        case BLK_ZS_IOPEN:
+            s->nr_zones_imp_open--;
+            break;
+        case BLK_ZS_EOPEN:
+            s->nr_zones_exp_open--;
+            break;
+        case BLK_ZS_CLOSED:
+            s->nr_zones_closed--;
+            break;
+        case BLK_ZS_FULL:
+            break;
+        default:
+            ret = -EINVAL;
+            goto unlock;
+        }
+
+        if (zs == BLK_ZS_EMPTY) {
+            continue;
+        }
+
+        *wp_i = ((uint64_t)index + i) * zone_size;
+        ret = qcow2_write_wp_at(bs, wp_i, index + i, BLK_ZS_EMPTY);
+        if (ret < 0) {
+            goto unlock;
+        }
+        /* clear data */
+        ret = qcow2_co_pwrite_zeroes(bs, qcow2_get_wp(*wp_i), zone_size, 0);
+        if (ret < 0) {
+            error_report("Failed to reset zone at 0x%" PRIx64 "", *wp_i);
+        }
+    }
+
+unlock:
+    qemu_co_mutex_unlock(&bs->wps->colock);
+    return ret;
+}
+
+static int coroutine_fn qcow2_co_zone_mgmt(BlockDriverState *bs, BlockZoneOp op,
+                                           int64_t offset, int64_t len)
+{
+    BDRVQcow2State *s = bs->opaque;
+    int ret = 0;
+    int64_t capacity = bs->total_sectors << BDRV_SECTOR_BITS;
+    int64_t zone_size = s->zoned_header.zone_size;
+    int64_t zone_size_mask = zone_size - 1;
+    uint32_t index = offset / zone_size;
+    BlockZoneWps *wps = bs->wps;
+
+    if (offset >= capacity) {
+        error_report("offset %" PRId64 " is equal to or greater than the"
+                     "device capacity %" PRId64 "", offset, capacity);
+        return -EINVAL;
+    }
+
+    if (offset & zone_size_mask) {
+        error_report("sector offset %" PRId64 " is not aligned to zone size"
+                     " %" PRId64 "", offset / 512, zone_size / 512);
+        return -EINVAL;
+    }
+
+    if (((offset + len) < capacity && len & zone_size_mask) ||
+        offset + len > capacity) {
+        error_report("number of sectors %" PRId64 " is not aligned to zone"
+                     " size %" PRId64 "", len / 512, zone_size / 512);
+        return -EINVAL;
+    }
+
+    qemu_co_mutex_lock(&wps->colock);
+    uint64_t wpv = wps->wp[index];
+    if (QCOW2_ZT_IS_CONV(wpv) && len != capacity) {
+        error_report("zone mgmt operations are not allowed for "
+                     "conventional zones");
+        ret = -EIO;
+        goto unlock;
+    }
+    qemu_co_mutex_unlock(&wps->colock);
+
+    switch(op) {
+    case BLK_ZO_OPEN:
+        ret = qcow2_open_zone(bs, index);
+        break;
+    case BLK_ZO_CLOSE:
+        ret = qcow2_close_zone(bs, index);
+        break;
+    case BLK_ZO_FINISH:
+        ret = qcow2_finish_zone(bs, index);
+        break;
+    case BLK_ZO_RESET:
+        ret = qcow2_reset_zone(bs, index, len);
+        break;
+    default:
+        error_report("Unsupported zone op: 0x%x", op);
+        ret = -ENOTSUP;
+        break;
+    }
+    return ret;
+
+unlock:
+    qemu_co_mutex_unlock(&wps->colock);
+    return ret;
+}
+
+static int coroutine_fn
+qcow2_co_zone_append(BlockDriverState *bs, int64_t *offset, QEMUIOVector *qiov,
+                     BdrvRequestFlags flags)
+{
+    assert(flags == 0);
+    int64_t capacity = bs->total_sectors << BDRV_SECTOR_BITS;
+    uint32_t index;
+    int ret;
+    int64_t zone_size_mask = bs->bl.zone_size - 1;
+    int64_t iov_len = 0;
+    int64_t len = 0;
+
+    if (*offset >= capacity) {
+        error_report("*offset %" PRId64 " is equal to or greater than the"
+                     "device capacity %" PRId64 "", *offset, capacity);
+        return -EINVAL;
+    }
+
+    /* offset + len should not pass the end of that zone starting from offset */
+    if (*offset & zone_size_mask) {
+        error_report("sector offset %" PRId64 " is not aligned to zone size "
+                     "%" PRId32 "", *offset / 512, bs->bl.zone_size / 512);
+        return -EINVAL;
+    }
+
+    int64_t wg = bs->bl.write_granularity;
+    int64_t wg_mask = wg - 1;
+    for (int i = 0; i < qiov->niov; i++) {
+        iov_len = qiov->iov[i].iov_len;
+        if (iov_len & wg_mask) {
+            error_report("len of IOVector[%d] %" PRId64 " is not aligned to "
+                         "block size %" PRId64 "", i, iov_len, wg);
+            return -EINVAL;
+        }
+    }
+    len = qiov->size;
+    index = *offset / bs->bl.zone_size;
+
+    if ((len >> BDRV_SECTOR_BITS) > bs->bl.max_append_sectors) {
+        return -ENOTSUP;
+    }
+
+    qemu_co_mutex_lock(&bs->wps->colock);
+    uint64_t wp = bs->wps->wp[index];
+    uint64_t wp_i = qcow2_get_wp(wp);
+    ret = qcow2_co_pwritev_part(bs, wp_i, len, qiov, 0, 0);
+    if (ret == 0) {
+        *offset = wp_i;
+        /* the zone state is set to full when the wp reaches the end */
+        uint64_t wp_final = bs->wps->wp[index];
+        if (!(qcow2_get_wp(wp_final) & zone_size_mask)) {
+            ret = qcow2_write_wp_at(bs, &wp_final, index, BLK_ZS_FULL);
+        }
+    } else {
+        error_report("qcow2: zap failed");
+    }
+
+    qemu_co_mutex_unlock(&bs->wps->colock);
+    return ret;
+}
+
 static int coroutine_fn GRAPH_RDLOCK
 qcow2_co_copy_range_from(BlockDriverState *bs,
                          BdrvChild *src, int64_t src_offset,
@@ -6287,6 +6936,10 @@ BlockDriver bdrv_qcow2 = {
     .bdrv_co_pwritev_part   = qcow2_co_pwritev_part,
     .bdrv_co_flush_to_os    = qcow2_co_flush_to_os,
 
+    .bdrv_co_zone_report    = qcow2_co_zone_report,
+    .bdrv_co_zone_mgmt    = qcow2_co_zone_mgmt,
+    .bdrv_co_zone_append    = qcow2_co_zone_append,
+
     .bdrv_co_pwrite_zeroes  = qcow2_co_pwrite_zeroes,
     .bdrv_co_pdiscard       = qcow2_co_pdiscard,
     .bdrv_co_copy_range_from = qcow2_co_copy_range_from,
diff --git a/block/qcow2.h b/block/qcow2.h
index edb8eebcb3..b1930ddbd2 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -247,6 +247,8 @@ typedef struct Qcow2ZonedHeaderExtension {
     uint32_t max_active_zones;
     uint32_t max_open_zones;
     uint32_t max_append_sectors;
+    uint64_t zonedmeta_offset;
+    uint64_t zonedmeta_size;
 } QEMU_PACKED Qcow2ZonedHeaderExtension;
 
 typedef struct Qcow2UnknownHeaderExtension {
diff --git a/block/trace-events b/block/trace-events
index 6f121b7636..06af0c79fb 100644
--- a/block/trace-events
+++ b/block/trace-events
@@ -82,6 +82,7 @@ qcow2_writev_data(void *co, uint64_t offset) "co %p offset 0x%" PRIx64
 qcow2_pwrite_zeroes_start_req(void *co, int64_t offset, int64_t bytes) "co %p offset 0x%" PRIx64 " bytes %" PRId64
 qcow2_pwrite_zeroes(void *co, int64_t offset, int64_t bytes) "co %p offset 0x%" PRIx64 " bytes %" PRId64
 qcow2_skip_cow(void *co, uint64_t offset, int nb_clusters) "co %p offset 0x%" PRIx64 " nb_clusters %d"
+qcow2_wp_tracking(int index, uint64_t wp) "wps[%d]: 0x%" PRIx64
 
 # qcow2-cluster.c
 qcow2_alloc_clusters_offset(void *co, uint64_t offset, int bytes) "co %p offset 0x%" PRIx64 " bytes %d"
diff --git a/docs/interop/qcow2.txt b/docs/interop/qcow2.txt
index 80ff03a826..e4002b443c 100644
--- a/docs/interop/qcow2.txt
+++ b/docs/interop/qcow2.txt
@@ -367,6 +367,12 @@ The fields of the zoned extension are:
                     The maximal data size in sectors of a zone
                     append request that can be issued to the device.
 
+          36 - 43:  zonedmeta_offset
+                    The offset of zoned metadata structure in the file in bytes.
+
+          44 - 51:  zonedmeta_size
+                    The size of zoned metadata in bytes.
+
 == Full disk encryption header pointer ==
 
 The full disk encryption header must be present if, and only if, the
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v3 4/4] iotests: test the zoned format feature for qcow2 file
  2023-08-28 15:09 [PATCH v3 0/4] Add full zoned storage emulation to qcow2 driver Sam Li
                   ` (2 preceding siblings ...)
  2023-08-28 15:09 ` [PATCH v3 3/4] qcow2: add zoned emulation capability Sam Li
@ 2023-08-28 15:09 ` Sam Li
  3 siblings, 0 replies; 13+ messages in thread
From: Sam Li @ 2023-08-28 15:09 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, dmitry.fomichev, Hanna Reitz, Markus Armbruster,
	Eric Blake, hare, qemu-block, stefanha, dlemoal, Sam Li

The zoned format feature can be tested by:
$ tests/qemu-iotests/check -qcow2 zoned-qcow2

Signed-off-by: Sam Li <faithilikerun@gmail.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 tests/qemu-iotests/tests/zoned-qcow2     | 135 ++++++++++++++++++++++
 tests/qemu-iotests/tests/zoned-qcow2.out | 140 +++++++++++++++++++++++
 2 files changed, 275 insertions(+)
 create mode 100755 tests/qemu-iotests/tests/zoned-qcow2
 create mode 100644 tests/qemu-iotests/tests/zoned-qcow2.out

diff --git a/tests/qemu-iotests/tests/zoned-qcow2 b/tests/qemu-iotests/tests/zoned-qcow2
new file mode 100755
index 0000000000..7ec8b18860
--- /dev/null
+++ b/tests/qemu-iotests/tests/zoned-qcow2
@@ -0,0 +1,135 @@
+#!/usr/bin/env bash
+#
+# Test zone management operations for qcow2 file.
+#
+
+seq="$(basename $0)"
+echo "QA output created by $seq"
+status=1 # failure is the default!
+
+file_name="zbc.qcow2"
+_cleanup()
+{
+  _cleanup_test_img
+  _rm_test_img "$file_name"
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ../common.rc
+. ../common.filter
+. ../common.qemu
+
+# This test only runs on Linux hosts with qcow2 image files.
+_supported_fmt qcow2
+_supported_proto file
+_supported_os Linux
+
+echo
+echo "=== Initial image setup ==="
+echo
+
+$QEMU_IMG create -f qcow2 $file_name -o size=768M -o zone_size=64M \
+-o zone_capacity=64M -o nr_conv_zones=0 -o max_append_sectors=131072 \
+-o max_open_zones=0 -o max_active_zones=0 -o zone_model=1
+
+IMG="--image-opts -n driver=qcow2,file.driver=file,file.filename=$file_name"
+QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT
+
+echo
+echo "=== Testing a qcow2 img with zoned format ==="
+echo
+echo "case 1: test if one zone operation works"
+
+echo "(1) report zones[0]:"
+$QEMU_IO $IMG -c "zrp 0 1"
+echo
+echo "report zones[0~9]:"
+$QEMU_IO $IMG -c "zrp 0 10"
+echo
+echo "report the last zone:"
+$QEMU_IO $IMG -c "zrp 0x2C000000 2" # 0x2C000000 / 512 = 0x160000
+echo
+echo
+echo "open zones[0]:"
+$QEMU_IO $IMG -c "zo 0 0x4000000" # 0x4000000 / 512 = 0x20000
+$QEMU_IO $IMG -c "zrp 0 1"
+echo
+echo "open zones[1]"
+$QEMU_IO $IMG -c "zo 0x4000000 0x4000000"
+$QEMU_IO $IMG -c "zrp 0x4000000 1"
+echo
+echo "open the last zone"
+$QEMU_IO $IMG -c "zo 0x2C000000 0x4000000"
+$QEMU_IO $IMG -c "zrp 0x2C000000 2"
+echo
+echo
+echo "close zones[0]"
+$QEMU_IO $IMG -c "zc 0 0x4000000"
+$QEMU_IO $IMG -c "zrp 0 1"
+echo
+echo "close the last zone"
+$QEMU_IO $IMG -c "zc 0x3e70000000 0x4000000"
+$QEMU_IO $IMG -c "zrp 0x3e70000000 2"
+echo
+echo
+echo "(4) finish zones[1]"
+$QEMU_IO $IMG -c "zf 0x4000000 0x4000000"
+$QEMU_IO $IMG -c "zrp 0x4000000 1"
+echo
+echo
+echo "(5) reset zones[1]"
+$QEMU_IO $IMG -c "zrs 0x4000000 0x4000000"
+$QEMU_IO $IMG -c "zrp 0x4000000 1"
+echo
+echo
+echo "(6) append write with (4k, 8k) data" # the physical block size of the device is 4096
+$QEMU_IO $IMG -c "zrp 0 12"
+echo "Append write zones[0] one time:"
+$QEMU_IO $IMG -c "zap -p 0 0x1000 0x2000"
+$QEMU_IO $IMG -c "zrp 0 1"
+echo
+echo "Append write zones[0] twice:"
+$QEMU_IO $IMG -c "zap -p 0 0x1000 0x2000"
+$QEMU_IO $IMG -c "zrp 0 1"
+echo
+echo "Append write zones[1] one time:"
+$QEMU_IO $IMG -c "zap -p 0x4000000 0x1000 0x2000"
+$QEMU_IO $IMG -c "zrp 0x4000000 1"
+echo
+echo "Append write zones[1] twice:"
+$QEMU_IO $IMG -c "zap -p 0x4000000 0x1000 0x2000"
+$QEMU_IO $IMG -c "zrp 0x4000000 1"
+echo
+echo "Reset all:"
+$QEMU_IO $IMG -c "zrs 0 768M"
+$QEMU_IO $IMG -c "zrp 0 12"
+echo
+echo
+echo "case 2: test a sets of ops that works or not"
+
+echo "(1) append write (4k, 4k) and then write to full"
+$QEMU_IO $IMG -c "zap -p 0 0x1000 0x1000"
+echo "wrote (4k, 4k):"
+$QEMU_IO $IMG -c "zrp 0 1"
+$QEMU_IO $IMG -c "zap -p 0 0x1000 0x3ffd000"
+echo "wrote to full:"
+$QEMU_IO $IMG -c "zrp 0 1"
+echo "Reset zones[0]:"
+$QEMU_IO $IMG -c "zrs 0 64M"
+$QEMU_IO $IMG -c "zrp 0 1"
+
+echo "(2) write in zones[0], zones[3], zones[8], and then reset all"
+$QEMU_IO $IMG -c "zap -p 0 0x1000 0x1000"
+$QEMU_IO $IMG -c "zap -p 0xc000000 0x1000 0x1000"
+$QEMU_IO $IMG -c "zap -p 0x20000000 0x1000 0x1000"
+echo "wrote three zones:"
+$QEMU_IO $IMG -c "zrp 0 12"
+echo "Reset all:"
+$QEMU_IO $IMG -c "zrs 0 768M"
+$QEMU_IO $IMG -c "zrp 0 12"
+
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
diff --git a/tests/qemu-iotests/tests/zoned-qcow2.out b/tests/qemu-iotests/tests/zoned-qcow2.out
new file mode 100644
index 0000000000..78f9c574c2
--- /dev/null
+++ b/tests/qemu-iotests/tests/zoned-qcow2.out
@@ -0,0 +1,140 @@
+QA output created by zoned-qcow2
+
+=== Initial image setup ===
+
+Formatting 'zbc.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib zone_model=1 zone_size=67108864 zone_capacity=67108864 nr_conv_zones=0 max_append_sectors=131072 max_active_zones=0 max_open_zones=0 size=805306368 lazy_refcounts=off refcount_bits=16
+
+=== Testing a qcow2 img with zoned format ===
+
+case 1: test if one zone operation works
+(1) report zones[0]:
+start: 0x0, len 0x20000, cap 0x20000, wptr 0x0, zcond:1, [type: 2]
+
+report zones[0~9]:
+start: 0x0, len 0x20000, cap 0x20000, wptr 0x0, zcond:1, [type: 2]
+start: 0x20000, len 0x20000, cap 0x20000, wptr 0x20000, zcond:1, [type: 2]
+start: 0x40000, len 0x20000, cap 0x20000, wptr 0x40000, zcond:1, [type: 2]
+start: 0x60000, len 0x20000, cap 0x20000, wptr 0x60000, zcond:1, [type: 2]
+start: 0x80000, len 0x20000, cap 0x20000, wptr 0x80000, zcond:1, [type: 2]
+start: 0xa0000, len 0x20000, cap 0x20000, wptr 0xa0000, zcond:1, [type: 2]
+start: 0xc0000, len 0x20000, cap 0x20000, wptr 0xc0000, zcond:1, [type: 2]
+start: 0xe0000, len 0x20000, cap 0x20000, wptr 0xe0000, zcond:1, [type: 2]
+start: 0x100000, len 0x20000, cap 0x20000, wptr 0x100000, zcond:1, [type: 2]
+start: 0x120000, len 0x20000, cap 0x20000, wptr 0x120000, zcond:1, [type: 2]
+
+report the last zone:
+start: 0x160000, len 0x20000, cap 0x20000, wptr 0x160000, zcond:1, [type: 2]
+
+
+open zones[0]:
+start: 0x0, len 0x20000, cap 0x20000, wptr 0x0, zcond:3, [type: 2]
+
+open zones[1]
+start: 0x20000, len 0x20000, cap 0x20000, wptr 0x20000, zcond:3, [type: 2]
+
+open the last zone
+start: 0x160000, len 0x20000, cap 0x20000, wptr 0x160000, zcond:3, [type: 2]
+
+
+close zones[0]
+start: 0x0, len 0x20000, cap 0x20000, wptr 0x0, zcond:4, [type: 2]
+
+close the last zone
+zone close failed: Input/output error
+qemu-io: offset 268167020544 is equal to or greater than thedevice capacity 805306368
+zone report failed: Invalid argument
+
+
+(4) finish zones[1]
+start: 0x20000, len 0x20000, cap 0x20000, wptr 0x40000, zcond:14, [type: 2]
+
+
+(5) reset zones[1]
+start: 0x20000, len 0x20000, cap 0x20000, wptr 0x20000, zcond:1, [type: 2]
+
+
+(6) append write with (4k, 8k) data
+start: 0x0, len 0x20000, cap 0x20000, wptr 0x0, zcond:4, [type: 2]
+start: 0x20000, len 0x20000, cap 0x20000, wptr 0x20000, zcond:1, [type: 2]
+start: 0x40000, len 0x20000, cap 0x20000, wptr 0x40000, zcond:1, [type: 2]
+start: 0x60000, len 0x20000, cap 0x20000, wptr 0x60000, zcond:1, [type: 2]
+start: 0x80000, len 0x20000, cap 0x20000, wptr 0x80000, zcond:1, [type: 2]
+start: 0xa0000, len 0x20000, cap 0x20000, wptr 0xa0000, zcond:1, [type: 2]
+start: 0xc0000, len 0x20000, cap 0x20000, wptr 0xc0000, zcond:1, [type: 2]
+start: 0xe0000, len 0x20000, cap 0x20000, wptr 0xe0000, zcond:1, [type: 2]
+start: 0x100000, len 0x20000, cap 0x20000, wptr 0x100000, zcond:1, [type: 2]
+start: 0x120000, len 0x20000, cap 0x20000, wptr 0x120000, zcond:1, [type: 2]
+start: 0x140000, len 0x20000, cap 0x20000, wptr 0x140000, zcond:1, [type: 2]
+start: 0x160000, len 0x20000, cap 0x20000, wptr 0x160000, zcond:3, [type: 2]
+Append write zones[0] one time:
+After zap done, the append sector is 0x0
+start: 0x0, len 0x20000, cap 0x20000, wptr 0x18, zcond:2, [type: 2]
+
+Append write zones[0] twice:
+After zap done, the append sector is 0x18
+start: 0x0, len 0x20000, cap 0x20000, wptr 0x30, zcond:2, [type: 2]
+
+Append write zones[1] one time:
+After zap done, the append sector is 0x20000
+start: 0x20000, len 0x20000, cap 0x20000, wptr 0x20018, zcond:2, [type: 2]
+
+Append write zones[1] twice:
+After zap done, the append sector is 0x20018
+start: 0x20000, len 0x20000, cap 0x20000, wptr 0x20030, zcond:2, [type: 2]
+
+Reset all:
+start: 0x0, len 0x20000, cap 0x20000, wptr 0x0, zcond:1, [type: 2]
+start: 0x20000, len 0x20000, cap 0x20000, wptr 0x20000, zcond:1, [type: 2]
+start: 0x40000, len 0x20000, cap 0x20000, wptr 0x40000, zcond:1, [type: 2]
+start: 0x60000, len 0x20000, cap 0x20000, wptr 0x60000, zcond:1, [type: 2]
+start: 0x80000, len 0x20000, cap 0x20000, wptr 0x80000, zcond:1, [type: 2]
+start: 0xa0000, len 0x20000, cap 0x20000, wptr 0xa0000, zcond:1, [type: 2]
+start: 0xc0000, len 0x20000, cap 0x20000, wptr 0xc0000, zcond:1, [type: 2]
+start: 0xe0000, len 0x20000, cap 0x20000, wptr 0xe0000, zcond:1, [type: 2]
+start: 0x100000, len 0x20000, cap 0x20000, wptr 0x100000, zcond:1, [type: 2]
+start: 0x120000, len 0x20000, cap 0x20000, wptr 0x120000, zcond:1, [type: 2]
+start: 0x140000, len 0x20000, cap 0x20000, wptr 0x140000, zcond:1, [type: 2]
+start: 0x160000, len 0x20000, cap 0x20000, wptr 0x160000, zcond:1, [type: 2]
+
+
+case 2: test a sets of ops that works or not
+(1) append write (4k, 4k) and then write to full
+After zap done, the append sector is 0x0
+wrote (4k, 4k):
+start: 0x0, len 0x20000, cap 0x20000, wptr 0x10, zcond:2, [type: 2]
+After zap done, the append sector is 0x10
+wrote to full:
+start: 0x0, len 0x20000, cap 0x20000, wptr 0x20000, zcond:14, [type: 2]
+Reset zones[0]:
+start: 0x0, len 0x20000, cap 0x20000, wptr 0x0, zcond:1, [type: 2]
+(2) write in zones[0], zones[3], zones[8], and then reset all
+After zap done, the append sector is 0x0
+After zap done, the append sector is 0x60000
+After zap done, the append sector is 0x100000
+wrote three zones:
+start: 0x0, len 0x20000, cap 0x20000, wptr 0x10, zcond:2, [type: 2]
+start: 0x20000, len 0x20000, cap 0x20000, wptr 0x20000, zcond:1, [type: 2]
+start: 0x40000, len 0x20000, cap 0x20000, wptr 0x40000, zcond:1, [type: 2]
+start: 0x60000, len 0x20000, cap 0x20000, wptr 0x60010, zcond:2, [type: 2]
+start: 0x80000, len 0x20000, cap 0x20000, wptr 0x80000, zcond:1, [type: 2]
+start: 0xa0000, len 0x20000, cap 0x20000, wptr 0xa0000, zcond:1, [type: 2]
+start: 0xc0000, len 0x20000, cap 0x20000, wptr 0xc0000, zcond:1, [type: 2]
+start: 0xe0000, len 0x20000, cap 0x20000, wptr 0xe0000, zcond:1, [type: 2]
+start: 0x100000, len 0x20000, cap 0x20000, wptr 0x100010, zcond:2, [type: 2]
+start: 0x120000, len 0x20000, cap 0x20000, wptr 0x120000, zcond:1, [type: 2]
+start: 0x140000, len 0x20000, cap 0x20000, wptr 0x140000, zcond:1, [type: 2]
+start: 0x160000, len 0x20000, cap 0x20000, wptr 0x160000, zcond:1, [type: 2]
+Reset all:
+start: 0x0, len 0x20000, cap 0x20000, wptr 0x0, zcond:1, [type: 2]
+start: 0x20000, len 0x20000, cap 0x20000, wptr 0x20000, zcond:1, [type: 2]
+start: 0x40000, len 0x20000, cap 0x20000, wptr 0x40000, zcond:1, [type: 2]
+start: 0x60000, len 0x20000, cap 0x20000, wptr 0x60000, zcond:1, [type: 2]
+start: 0x80000, len 0x20000, cap 0x20000, wptr 0x80000, zcond:1, [type: 2]
+start: 0xa0000, len 0x20000, cap 0x20000, wptr 0xa0000, zcond:1, [type: 2]
+start: 0xc0000, len 0x20000, cap 0x20000, wptr 0xc0000, zcond:1, [type: 2]
+start: 0xe0000, len 0x20000, cap 0x20000, wptr 0xe0000, zcond:1, [type: 2]
+start: 0x100000, len 0x20000, cap 0x20000, wptr 0x100000, zcond:1, [type: 2]
+start: 0x120000, len 0x20000, cap 0x20000, wptr 0x120000, zcond:1, [type: 2]
+start: 0x140000, len 0x20000, cap 0x20000, wptr 0x140000, zcond:1, [type: 2]
+start: 0x160000, len 0x20000, cap 0x20000, wptr 0x160000, zcond:1, [type: 2]
+*** done
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 2/4] qcow2: add configurations for zoned format extension
  2023-08-28 15:09 ` [PATCH v3 2/4] qcow2: add configurations for zoned format extension Sam Li
@ 2023-09-01 11:07   ` Markus Armbruster
  2023-09-18  8:24     ` Sam Li
  2023-09-13 20:12   ` Stefan Hajnoczi
  1 sibling, 1 reply; 13+ messages in thread
From: Markus Armbruster @ 2023-09-01 11:07 UTC (permalink / raw)
  To: Sam Li
  Cc: qemu-devel, Kevin Wolf, dmitry.fomichev, Hanna Reitz, Eric Blake,
	hare, qemu-block, stefanha, dlemoal

Sam Li <faithilikerun@gmail.com> writes:

> To configure the zoned format feature on the qcow2 driver, it
> requires following arguments: the device size, zoned profile,

"Zoned profile" is gone in v3.

> zone model, zone size, zone capacity, number of conventional
> zones, limits on zone resources (max append sectors, max open
> zones, and max_active_zones).
>
> To create a qcow2 file with zoned format, use command like this:
> $ qemu-img create -f qcow2 test.qcow2 -o size=768M -o
> zone_size=64M -o zone_capacity=64M -o nr_conv_zones=0 -o
> max_append_sectors=512 -o max_open_zones=0 -o max_active_zones=0
> -o zone_model=1
>
> Signed-off-by: Sam Li <faithilikerun@gmail.com>

[...]

> diff --git a/qapi/block-core.json b/qapi/block-core.json
> index 2b1d493d6e..0d8f9e0a88 100644
> --- a/qapi/block-core.json
> +++ b/qapi/block-core.json
> @@ -5021,6 +5021,27 @@
>  # @compression-type: The image cluster compression method
>  #     (default: zlib, since 5.1)
>  #
> +# @zone-model: Zoned device model, 1 for host-managed and 0 for

Why is this encoded as a number?

If it's fundamentally a flag, use bool.

If more models could appear in the future, make it an enum.

> +#     non-zoned devices (default: 0, since 8.0)

Since 8.2.  More of the same below.

> +#
> +# @zone-size: Total number of logical blocks within zones in bytes
> +#     (since 8.0)
> +#
> +# @zone-capacity: The number of usable logical blocks within zones
> +#     in bytes. A zone capacity is always smaller or equal to the
> +#     zone size. (since 8.0)

Two spaces between sentences for consistency, please.

> +#
> +# @nr-conv-zones: The number of conventional zones of the zoned device
> +#     (since 8.0)

I think @conventional-zones would be more obvious.

> +#
> +# @max-open-zones: The maximal allowed open zones (since 8.0)

Maybe "The maximum number of open zones".

> +#
> +# @max-active-zones: The limit of the zones that have the implicit
> +#     open, explicit open or closed state (since 8.0)

Maybe "The maximum number of zones in the implicit open, explicit open
or closed state".

> +#
> +# @max-append-sectors: The maximal data size in sectors of a zone
> +#     append request that can be issued to the device. (since 8.0)

What's the sector size, and how can the user determine it?  Why can't we
use bytes here?

> +#
>  # Since: 2.12
>  ##
>  { 'struct': 'BlockdevCreateOptionsQcow2',
> @@ -5037,7 +5058,14 @@
>              '*preallocation':   'PreallocMode',
>              '*lazy-refcounts':  'bool',
>              '*refcount-bits':   'int',
> -            '*compression-type':'Qcow2CompressionType' } }
> +            '*compression-type':'Qcow2CompressionType',
> +            '*zone-model':         'uint8',
> +            '*zone-size':          'size',
> +            '*zone-capacity':      'size',
> +            '*nr-conv-zones':      'uint32',
> +            '*max-open-zones':     'uint32',
> +            '*max-active-zones':   'uint32',
> +            '*max-append-sectors': 'uint32' } }
>  
>  ##
>  # @BlockdevCreateOptionsQed:



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 1/4] docs/qcow2: add the zoned format feature
  2023-08-28 15:09 ` [PATCH v3 1/4] docs/qcow2: add the zoned format feature Sam Li
@ 2023-09-06 20:26   ` Stefan Hajnoczi
  0 siblings, 0 replies; 13+ messages in thread
From: Stefan Hajnoczi @ 2023-09-06 20:26 UTC (permalink / raw)
  To: Sam Li
  Cc: qemu-devel, Kevin Wolf, dmitry.fomichev, Hanna Reitz,
	Markus Armbruster, Eric Blake, hare, qemu-block, dlemoal

[-- Attachment #1: Type: text/plain, Size: 3051 bytes --]

On Mon, Aug 28, 2023 at 11:09:52PM +0800, Sam Li wrote:
> Add the specs for the zoned format feature of the qcow2 driver.
> The qcow2 file can be taken as zoned device and passed through by
> virtio-blk device or NVMe ZNS device to the guest given zoned
> information.
> 
> Signed-off-by: Sam Li <faithilikerun@gmail.com>
> ---
>  docs/system/qemu-block-drivers.rst.inc | 39 ++++++++++++++++++++++++++
>  1 file changed, 39 insertions(+)
> 
> diff --git a/docs/system/qemu-block-drivers.rst.inc b/docs/system/qemu-block-drivers.rst.inc
> index 105cb9679c..640ab151a7 100644
> --- a/docs/system/qemu-block-drivers.rst.inc
> +++ b/docs/system/qemu-block-drivers.rst.inc
> @@ -172,6 +172,45 @@ This section describes each format and the options that are supported for it.
>      filename`` to check if the NOCOW flag is set or not (Capital 'C' is
>      NOCOW flag).
>  
> +  .. option:: zoned
> +    The zoned interface of zoned storage divices can different forms which
> +    is referred to as models. This option uses number to represent, 1 for
> +    host-managed and 0 for non-zoned.

I would simplify this paragraph down to:

  1 for a host-managed zoned device or 0 for a non-zoned device.

> +
> +  .. option:: zone_size
> +
> +    The size of a zone of the zoned device in bytes. The device is divided

The first sentence is a little confusing due to the repetition of the
word "zone". It can be shortened:

  The size of a zone, in bytes.

> +    into zones of this size with the exception of the last zone, which may
> +    be smaller.
> +
> +  .. option:: zone_capacity
> +
> +    The initial capacity value for all zones. The capacity must be less than

  The initial capacity value, in bytes, for all zones.

> +    or equal to zone size. If the last zone is smaller, then its capacity is
> +    capped. The device follows the ZBC protocol tends to have the same size
> +    as its zone.

I think the last sentence says that ZBC devices tend to have capacity ==
len whereas ZNS devices may have a unique capacity for each zone? You
could drop this last sentence completely.

> +
> +    The zone capacity is per zone and may be different between zones in real
> +    devices. For simplicity, limits QCow2 emulation to the same zone capacity
> +    for all zones.

The last sentence:

  For simplicity, qcow2 sets all zones to the same capacity.

> +
> +  .. option:: zone_nr_conv
> +
> +    The number of conventional zones of the zoned device.
> +
> +  .. option:: max_open_zones
> +
> +    The maximal allowed open zones.
> +
> +  .. option:: max_active_zones
> +
> +    The limit of the zones with implicit open, explicit open or closed state.
> +
> +  .. option:: max_append_sectors
> +
> +    The maximal sectors in 512B blocks that is allowed to append to zones
> +    while writing.

Rephrasing:

  The maximum number of 512-byte sectors in a zone append request.

> +
>  .. program:: image-formats
>  .. option:: qed
>  
> -- 
> 2.40.1
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 2/4] qcow2: add configurations for zoned format extension
  2023-08-28 15:09 ` [PATCH v3 2/4] qcow2: add configurations for zoned format extension Sam Li
  2023-09-01 11:07   ` Markus Armbruster
@ 2023-09-13 20:12   ` Stefan Hajnoczi
  2023-09-18  8:55     ` Sam Li
  1 sibling, 1 reply; 13+ messages in thread
From: Stefan Hajnoczi @ 2023-09-13 20:12 UTC (permalink / raw)
  To: Sam Li
  Cc: qemu-devel, Kevin Wolf, dmitry.fomichev, Hanna Reitz,
	Markus Armbruster, Eric Blake, hare, qemu-block, dlemoal

[-- Attachment #1: Type: text/plain, Size: 20175 bytes --]

On Mon, Aug 28, 2023 at 11:09:53PM +0800, Sam Li wrote:
> To configure the zoned format feature on the qcow2 driver, it
> requires following arguments: the device size, zoned profile,
> zone model, zone size, zone capacity, number of conventional
> zones, limits on zone resources (max append sectors, max open
> zones, and max_active_zones).
> 
> To create a qcow2 file with zoned format, use command like this:
> $ qemu-img create -f qcow2 test.qcow2 -o size=768M -o
> zone_size=64M -o zone_capacity=64M -o nr_conv_zones=0 -o
> max_append_sectors=512 -o max_open_zones=0 -o max_active_zones=0
> -o zone_model=1
> 
> Signed-off-by: Sam Li <faithilikerun@gmail.com>
> ---
>  block/qcow2.c                    | 176 ++++++++++++++++++++++++++++++-
>  block/qcow2.h                    |  20 ++++
>  docs/interop/qcow2.txt           |  36 +++++++
>  include/block/block_int-common.h |  13 +++
>  qapi/block-core.json             |  30 +++++-
>  5 files changed, 273 insertions(+), 2 deletions(-)
> 
> diff --git a/block/qcow2.c b/block/qcow2.c
> index c51388e99d..7074bfc620 100644
> --- a/block/qcow2.c
> +++ b/block/qcow2.c
> @@ -73,6 +73,7 @@ typedef struct {
>  #define  QCOW2_EXT_MAGIC_CRYPTO_HEADER 0x0537be77
>  #define  QCOW2_EXT_MAGIC_BITMAPS 0x23852875
>  #define  QCOW2_EXT_MAGIC_DATA_FILE 0x44415441
> +#define  QCOW2_EXT_MAGIC_ZONED_FORMAT 0x7a6264
>  
>  static int coroutine_fn
>  qcow2_co_preadv_compressed(BlockDriverState *bs,
> @@ -210,6 +211,7 @@ qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
>      uint64_t offset;
>      int ret;
>      Qcow2BitmapHeaderExt bitmaps_ext;
> +    Qcow2ZonedHeaderExtension zoned_ext;
>  
>      if (need_update_header != NULL) {
>          *need_update_header = false;
> @@ -431,6 +433,55 @@ qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
>              break;
>          }
>  
> +        case QCOW2_EXT_MAGIC_ZONED_FORMAT:
> +        {
> +            if (ext.len != sizeof(zoned_ext)) {
> +                error_setg(errp, "zoned_ext: Invalid extension length");
> +                return -EINVAL;
> +            }
> +            ret = bdrv_pread(bs->file, offset, ext.len, &zoned_ext, 0);
> +            if (ret < 0) {
> +                error_setg_errno(errp, -ret, "zoned_ext: "
> +                                             "Could not read ext header");
> +                return ret;
> +            }
> +
> +            zoned_ext.zone_size = be32_to_cpu(zoned_ext.zone_size);
> +            zoned_ext.zone_capacity = be32_to_cpu(zoned_ext.zone_capacity);
> +            zoned_ext.nr_conv_zones = be32_to_cpu(zoned_ext.nr_conv_zones);
> +            zoned_ext.nr_zones = be32_to_cpu(zoned_ext.nr_zones);
> +            zoned_ext.max_open_zones = be32_to_cpu(zoned_ext.max_open_zones);
> +            zoned_ext.max_active_zones =
> +                be32_to_cpu(zoned_ext.max_active_zones);
> +            zoned_ext.max_append_sectors =
> +                be32_to_cpu(zoned_ext.max_append_sectors);
> +            s->zoned_header = zoned_ext;
> +
> +            /* refuse to open broken images */
> +            if (zoned_ext.zone_size == 0) {
> +                error_setg(errp, "Zoned extension header zone_size field "
> +                                 "can not be 0");
> +                return -EINVAL;
> +            }
> +            if (zoned_ext.zone_capacity > zoned_ext.zone_size) {
> +                error_setg(errp, "Zoned extension header zone_capacity field "
> +                                 "can not be larger that zone_size field");
> +                return -EINVAL;
> +            }
> +            if (zoned_ext.nr_zones != DIV_ROUND_UP(
> +                bs->total_sectors * BDRV_SECTOR_SIZE, zoned_ext.zone_size)) {
> +                error_setg(errp, "Zoned extension header nr_zones field "
> +                                 "gets wrong");

"gets" -> "is"

> +                return -EINVAL;
> +            }
> +
> +#ifdef DEBUG_EXT
> +            printf("Qcow2: Got zoned format extension: "
> +                   "offset=%" PRIu32 "\n", offset);
> +#endif
> +            break;
> +        }
> +
>          default:
>              /* unknown magic - save it in case we need to rewrite the header */
>              /* If you add a new feature, make sure to also update the fast
> @@ -1967,6 +2018,14 @@ static void qcow2_refresh_limits(BlockDriverState *bs, Error **errp)
>      }
>      bs->bl.pwrite_zeroes_alignment = s->subcluster_size;
>      bs->bl.pdiscard_alignment = s->cluster_size;
> +    bs->bl.zoned = s->zoned_header.zoned;
> +    bs->bl.nr_zones = s->zoned_header.nr_zones;
> +    bs->wps = s->wps;
> +    bs->bl.max_append_sectors = s->zoned_header.max_append_sectors;
> +    bs->bl.max_active_zones = s->zoned_header.max_active_zones;
> +    bs->bl.max_open_zones = s->zoned_header.max_open_zones;
> +    bs->bl.zone_size = s->zoned_header.zone_size;
> +    bs->bl.write_granularity = BDRV_SECTOR_SIZE;
>  }
>  
>  static int qcow2_reopen_prepare(BDRVReopenState *state,
> @@ -3089,6 +3148,30 @@ int qcow2_update_header(BlockDriverState *bs)
>          buflen -= ret;
>      }
>  
> +    /* Zoned devices header extension */
> +    if (s->zoned_header.zoned == BLK_Z_HM) {
> +        Qcow2ZonedHeaderExtension zoned_header = {
> +            .zoned              = s->zoned_header.zoned,
> +            .zone_size          = cpu_to_be32(s->zoned_header.zone_size),
> +            .zone_capacity      = cpu_to_be32(s->zoned_header.zone_capacity),
> +            .nr_conv_zones      = cpu_to_be32(s->zoned_header.nr_conv_zones),
> +            .nr_zones           = cpu_to_be32(s->zoned_header.nr_zones),
> +            .max_open_zones     = cpu_to_be32(s->zoned_header.max_open_zones),
> +            .max_active_zones   =
> +                cpu_to_be32(s->zoned_header.max_active_zones),
> +            .max_append_sectors =
> +                cpu_to_be32(s->zoned_header.max_append_sectors)
> +        };
> +        ret = header_ext_add(buf, QCOW2_EXT_MAGIC_ZONED_FORMAT,
> +                             &zoned_header, sizeof(zoned_header),
> +                             buflen);
> +        if (ret < 0) {
> +            goto fail;
> +        }
> +        buf += ret;
> +        buflen -= ret;
> +    }
> +
>      /* Keep unknown header extensions */
>      QLIST_FOREACH(uext, &s->unknown_header_ext, next) {
>          ret = header_ext_add(buf, uext->magic, uext->data, uext->len, buflen);
> @@ -3768,11 +3851,60 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
>      }
>  
>      /* Set the external data file if necessary */
> +    BDRVQcow2State *s = blk_bs(blk)->opaque;
>      if (data_bs) {
> -        BDRVQcow2State *s = blk_bs(blk)->opaque;
>          s->image_data_file = g_strdup(data_bs->filename);
>      }
>  
> +    if (qcow2_opts->has_zone_model && qcow2_opts->zone_model == BLK_Z_HM) {
> +        if (qcow2_opts->has_zone_size && qcow2_opts->zone_size == 0) {

has_zone_size is mandatory:

  if (!qcow2_opts->has_zone_size) {
      error_setg(errp, "Missing zone_size parameter");
      ret = -EINVAL;
      goto out;
  }
  ...

> +            s->zoned_header.zoned = BLK_Z_NONE;
> +            error_setg(errp, "Zoned devices can not allow a larger-than-zero "
> +                             "zone_size");

Missing "ret = -EINVAL;".

> +            goto out;
> +        }
> +        s->zoned_header.zoned = qcow2_opts->zone_model;
> +        s->zoned_header.zone_size = qcow2_opts->zone_size;
> +        s->zoned_header.nr_zones = DIV_ROUND_UP(qcow2_opts->size,
> +                                                qcow2_opts->zone_size);
> +
> +        if (qcow2_opts->has_zone_capacity) {
> +            if (qcow2_opts->zone_capacity > qcow2_opts->zone_size) {
> +                s->zoned_header.zoned = BLK_Z_NONE;
> +                error_setg(errp, "zone capacity %" PRIu64 "B exceeds zone size "
> +                           "%" PRIu64"B", qcow2_opts->zone_capacity,
> +                           qcow2_opts->zone_size);

Missing "ret = -EINVAL;".

> +                goto out;
> +            }
> +            s->zoned_header.zone_capacity = qcow2_opts->zone_capacity;
> +        } else {
> +            s->zoned_header.zone_capacity = qcow2_opts->zone_size;
> +        }
> +
> +        if (qcow2_opts->has_nr_conv_zones) {
> +            s->zoned_header.nr_conv_zones = qcow2_opts->nr_conv_zones;
> +        }
> +
> +        if (qcow2_opts->has_max_active_zones) {
> +            if (qcow2_opts->max_open_zones > qcow2_opts->max_active_zones) {
> +                s->zoned_header.zoned = BLK_Z_NONE;
> +                error_setg(errp, "max_open_zones %" PRIu32 " exceeds "
> +                           "max_active_zones %" PRIu32"",
> +                           qcow2_opts->max_open_zones,
> +                           qcow2_opts->max_active_zones);

Missing "ret = -EINVAL;".

> +                goto out;
> +            }
> +            if (qcow2_opts->has_max_open_zones) {
> +                s->zoned_header.max_open_zones = qcow2_opts->max_active_zones;
> +            } else {
> +                s->zoned_header.max_open_zones = qcow2_opts->max_active_zones;
> +            }
> +        }
> +        s->zoned_header.max_append_sectors = qcow2_opts->max_append_sectors;
> +    } else {
> +        s->zoned_header.zoned = BLK_Z_NONE;
> +    }
> +
>      /* Create a full header (including things like feature table) */
>      ret = qcow2_update_header(blk_bs(blk));
>      bdrv_graph_co_rdunlock();
> @@ -3903,6 +4035,13 @@ qcow2_co_create_opts(BlockDriver *drv, const char *filename, QemuOpts *opts,
>          { BLOCK_OPT_COMPAT_LEVEL,       "version" },
>          { BLOCK_OPT_DATA_FILE_RAW,      "data-file-raw" },
>          { BLOCK_OPT_COMPRESSION_TYPE,   "compression-type" },
> +        { BLOCK_OPT_Z_MODEL,            "zone-model"},
> +        { BLOCK_OPT_Z_NR_COV,           "nr-conv-zones"},
> +        { BLOCK_OPT_Z_MOZ,              "max-open-zones"},
> +        { BLOCK_OPT_Z_MAZ,              "max-active-zones"},
> +        { BLOCK_OPT_Z_MAS,              "max-append-sectors"},
> +        { BLOCK_OPT_Z_SIZE,             "zone-size"},
> +        { BLOCK_OPT_Z_CAP,              "zone-capacity"},
>          { NULL, NULL },
>      };
>  
> @@ -6066,6 +6205,41 @@ static QemuOptsList qcow2_create_opts = {
>              .help = "Compression method used for image cluster "        \
>                      "compression",                                      \
>              .def_value_str = "zlib"                                     \
> +        },                                                              \
> +        {                                                               \
> +            .name = BLOCK_OPT_Z_MODEL,                                  \
> +            .type = QEMU_OPT_NUMBER,                                    \
> +            .help = "zone model",                                      \
> +        },                                                              \
> +        {                                                               \
> +            .name = BLOCK_OPT_Z_SIZE,                                   \
> +            .type = QEMU_OPT_SIZE,                                      \
> +            .help = "zone size",                                        \
> +        },                                                              \
> +        {                                                               \
> +            .name = BLOCK_OPT_Z_CAP,                                    \
> +            .type = QEMU_OPT_SIZE,                                      \
> +            .help = "zone capacity",                                    \
> +        },                                                              \
> +        {                                                               \
> +            .name = BLOCK_OPT_Z_NR_COV,                                 \
> +            .type = QEMU_OPT_NUMBER,                                    \
> +            .help = "numbers of conventional zones",                    \
> +        },                                                              \
> +        {                                                               \
> +            .name = BLOCK_OPT_Z_MAS,                                    \
> +            .type = QEMU_OPT_NUMBER,                                    \
> +            .help = "max append sectors",                               \
> +        },                                                              \
> +        {                                                               \
> +            .name = BLOCK_OPT_Z_MAZ,                                    \
> +            .type = QEMU_OPT_NUMBER,                                    \
> +            .help = "max active zones",                                 \
> +        },                                                              \
> +        {                                                               \
> +            .name = BLOCK_OPT_Z_MOZ,                                    \
> +            .type = QEMU_OPT_NUMBER,                                    \
> +            .help = "max open zones",                                   \
>          },
>          QCOW_COMMON_OPTIONS,
>          { /* end of list */ }
> diff --git a/block/qcow2.h b/block/qcow2.h
> index f789ce3ae0..edb8eebcb3 100644
> --- a/block/qcow2.h
> +++ b/block/qcow2.h
> @@ -236,6 +236,19 @@ typedef struct Qcow2CryptoHeaderExtension {
>      uint64_t length;
>  } QEMU_PACKED Qcow2CryptoHeaderExtension;
>  
> +typedef struct Qcow2ZonedHeaderExtension {
> +    /* Zoned device attributes */
> +    uint8_t zoned;
> +    uint8_t reserved[3];
> +    uint32_t zone_size;
> +    uint32_t zone_capacity;
> +    uint32_t nr_conv_zones;
> +    uint32_t nr_zones;
> +    uint32_t max_active_zones;
> +    uint32_t max_open_zones;
> +    uint32_t max_append_sectors;
> +} QEMU_PACKED Qcow2ZonedHeaderExtension;
> +
>  typedef struct Qcow2UnknownHeaderExtension {
>      uint32_t magic;
>      uint32_t len;
> @@ -422,6 +435,13 @@ typedef struct BDRVQcow2State {
>       * is to convert the image with the desired compression type set.
>       */
>      Qcow2CompressionType compression_type;
> +
> +    /* States of zoned device */
> +    Qcow2ZonedHeaderExtension zoned_header;
> +    uint32_t nr_zones_exp_open;
> +    uint32_t nr_zones_imp_open;
> +    uint32_t nr_zones_closed;
> +    BlockZoneWps *wps;

How does this relate to bs->wps? I think I've asked about this before.
I wonder if it's possible to avoid duplicating this field in two
structs.

>  } BDRVQcow2State;
>  
>  typedef struct Qcow2COWRegion {
> diff --git a/docs/interop/qcow2.txt b/docs/interop/qcow2.txt
> index 2c4618375a..80ff03a826 100644
> --- a/docs/interop/qcow2.txt
> +++ b/docs/interop/qcow2.txt
> @@ -331,6 +331,42 @@ The fields of the bitmaps extension are:
>                     Offset into the image file at which the bitmap directory
>                     starts. Must be aligned to a cluster boundary.
>  
> +== Zoned extension ==
> +
> +The zoned extension is an optional header extension. It contains fields for
> +emulating the zoned stroage model (https://zonedstorage.io/).
> +
> +The fields of the zoned extension are:
> +    Byte        0:  zoned
> +                    Zoned model, 1 for host-managed and 0 for non-zoned devices.
> +
> +            1 - 3:  Reserved, must be zero.
> +
> +            4 - 7:  zone_size
> +                    Total number of logical blocks within the zones in bytes.
> +
> +           8 - 11:  zone_capacity
> +                    The number of writable logical blocks within the zones in
> +                    bytes. A zone capacity is always smaller or equal to the
> +                    zone size.
> +
> +          12 - 15:  nr_conv_zones
> +                    The number of conventional zones.
> +
> +          16 - 19:  nr_zones
> +                    The number of zones.
> +
> +          20 - 23:  max_active_zones
> +                    The limit of the zones that have the implicit open,
> +                    explicit open or closed state.
> +
> +          24 - 27:  max_open_zones
> +                    The maximal allowed open zones.
> +
> +          28 - 35:  max_append_sectors
> +                    The maximal data size in sectors of a zone

512-byte sectors? Please clarify the size of the sectors.

> +                    append request that can be issued to the device.
> +
>  == Full disk encryption header pointer ==
>  
>  The full disk encryption header must be present if, and only if, the
> diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h
> index 74195c3004..332c0c765c 100644
> --- a/include/block/block_int-common.h
> +++ b/include/block/block_int-common.h
> @@ -57,6 +57,13 @@
>  #define BLOCK_OPT_DATA_FILE_RAW     "data_file_raw"
>  #define BLOCK_OPT_COMPRESSION_TYPE  "compression_type"
>  #define BLOCK_OPT_EXTL2             "extended_l2"
> +#define BLOCK_OPT_Z_MODEL           "zone_model"
> +#define BLOCK_OPT_Z_SIZE            "zone_size"
> +#define BLOCK_OPT_Z_CAP             "zone_capacity"
> +#define BLOCK_OPT_Z_NR_COV          "nr_conv_zones"
> +#define BLOCK_OPT_Z_MAS             "max_append_sectors"
> +#define BLOCK_OPT_Z_MAZ             "max_active_zones"
> +#define BLOCK_OPT_Z_MOZ             "max_open_zones"
>  
>  #define BLOCK_PROBE_BUF_SIZE        512
>  
> @@ -878,6 +885,12 @@ typedef struct BlockLimits {
>      /* zone size expressed in bytes */
>      uint32_t zone_size;
>  
> +    /*
> +     * the number of usable logical blocks within the zone, expressed
> +     * in bytes. A zone capacity is smaller or equal to the zone size.
> +     */
> +    uint32_t zone_capacity;
> +
>      /* total number of zones */
>      uint32_t nr_zones;
>  
> diff --git a/qapi/block-core.json b/qapi/block-core.json
> index 2b1d493d6e..0d8f9e0a88 100644
> --- a/qapi/block-core.json
> +++ b/qapi/block-core.json
> @@ -5021,6 +5021,27 @@
>  # @compression-type: The image cluster compression method
>  #     (default: zlib, since 5.1)
>  #
> +# @zone-model: Zoned device model, 1 for host-managed and 0 for
> +#     non-zoned devices (default: 0, since 8.0)
> +#
> +# @zone-size: Total number of logical blocks within zones in bytes
> +#     (since 8.0)
> +#
> +# @zone-capacity: The number of usable logical blocks within zones
> +#     in bytes. A zone capacity is always smaller or equal to the
> +#     zone size. (since 8.0)
> +#
> +# @nr-conv-zones: The number of conventional zones of the zoned device
> +#     (since 8.0)
> +#
> +# @max-open-zones: The maximal allowed open zones (since 8.0)
> +#
> +# @max-active-zones: The limit of the zones that have the implicit
> +#     open, explicit open or closed state (since 8.0)
> +#
> +# @max-append-sectors: The maximal data size in sectors of a zone
> +#     append request that can be issued to the device. (since 8.0)
> +#
>  # Since: 2.12
>  ##
>  { 'struct': 'BlockdevCreateOptionsQcow2',
> @@ -5037,7 +5058,14 @@
>              '*preallocation':   'PreallocMode',
>              '*lazy-refcounts':  'bool',
>              '*refcount-bits':   'int',
> -            '*compression-type':'Qcow2CompressionType' } }
> +            '*compression-type':'Qcow2CompressionType',
> +            '*zone-model':         'uint8',
> +            '*zone-size':          'size',
> +            '*zone-capacity':      'size',
> +            '*nr-conv-zones':      'uint32',
> +            '*max-open-zones':     'uint32',
> +            '*max-active-zones':   'uint32',
> +            '*max-append-sectors': 'uint32' } }
>  
>  ##
>  # @BlockdevCreateOptionsQed:
> -- 
> 2.40.1
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 3/4] qcow2: add zoned emulation capability
  2023-08-28 15:09 ` [PATCH v3 3/4] qcow2: add zoned emulation capability Sam Li
@ 2023-09-13 21:11   ` Stefan Hajnoczi
  0 siblings, 0 replies; 13+ messages in thread
From: Stefan Hajnoczi @ 2023-09-13 21:11 UTC (permalink / raw)
  To: Sam Li
  Cc: qemu-devel, Kevin Wolf, dmitry.fomichev, Hanna Reitz,
	Markus Armbruster, Eric Blake, hare, qemu-block, dlemoal

[-- Attachment #1: Type: text/plain, Size: 4734 bytes --]

On Mon, Aug 28, 2023 at 11:09:54PM +0800, Sam Li wrote:
> By adding zone operations and zoned metadata, the zoned emulation
> capability enables full emulation support of zoned device using
> a qcow2 file. The zoned device metadata includes zone type,
> zoned device state and write pointer of each zone, which is stored
> to an array of unsigned integers.
> 
> Each zone of a zoned device makes state transitions following
> the zone state machine. The zone state machine mainly describes
> five states, IMPLICIT OPEN, EXPLICIT OPEN, FULL, EMPTY and CLOSED.
> READ ONLY and OFFLINE states will generally be affected by device
> internal events. The operations on zones cause corresponding state
> changing.
> 
> Zoned devices have a limit on zone resources, which puts constraints on
> write operations into zones.
> 
> Signed-off-by: Sam Li <faithilikerun@gmail.com>
> ---
>  block/qcow2.c          | 657 ++++++++++++++++++++++++++++++++++++++++-
>  block/qcow2.h          |   2 +
>  block/trace-events     |   1 +
>  docs/interop/qcow2.txt |   6 +
>  4 files changed, 664 insertions(+), 2 deletions(-)
> 
> diff --git a/block/qcow2.c b/block/qcow2.c
> index 7074bfc620..bc98d98c8e 100644
> --- a/block/qcow2.c
> +++ b/block/qcow2.c
> @@ -194,6 +194,153 @@ qcow2_extract_crypto_opts(QemuOpts *opts, const char *fmt, Error **errp)
>      return cryptoopts_qdict;
>  }
>  
> +#define QCOW2_ZT_IS_CONV(wp)    (wp & 1ULL << 59)
> +
> +static inline int qcow2_get_wp(uint64_t wp)
> +{
> +    /* clear state and type information */
> +    return ((wp << 5) >> 5);
> +}
> +
> +static inline int qcow2_get_zs(uint64_t wp)
> +{
> +    return (wp >> 60);
> +}
> +
> +static inline void qcow2_set_zs(uint64_t *wp, BlockZoneState zs)
> +{
> +    uint64_t addr = qcow2_get_wp(*wp);
> +    addr |= ((uint64_t)zs << 60);
> +    *wp = addr;
> +}
> +
> +/*
> + * Perform a state assignment and a flush operation that writes the new wp
> + * value to the dedicated location of the disk file.
> + */
> +static int qcow2_write_wp_at(BlockDriverState *bs, uint64_t *wp,
> +                             uint32_t index, BlockZoneState zs) {
> +    BDRVQcow2State *s = bs->opaque;
> +    uint64_t wpv = *wp;
> +    int ret;
> +
> +    qcow2_set_zs(wp, zs);
> +    ret = bdrv_pwrite(bs->file, s->zoned_header.zonedmeta_offset
> +        + sizeof(uint64_t) * index, sizeof(uint64_t), wp, 0);
> +
> +    if (ret < 0) {
> +        goto exit;
> +    }
> +    trace_qcow2_wp_tracking(index, qcow2_get_wp(*wp) >> BDRV_SECTOR_BITS);
> +    return ret;
> +
> +exit:
> +    *wp = wpv;
> +    error_report("Failed to write metadata with file");
> +    return ret;
> +}
> +
> +static bool qcow2_check_active_zones(BlockDriverState *bs)
> +{
> +    BDRVQcow2State *s = bs->opaque;
> +
> +    if (!s->zoned_header.max_active_zones) {
> +        return true;
> +    }
> +
> +    if (s->nr_zones_exp_open + s->nr_zones_imp_open + s->nr_zones_closed
> +        < s->zoned_header.max_active_zones) {
> +        return true;
> +    }
> +
> +    return false;
> +}
> +
> +static int qcow2_check_open_zones(BlockDriverState *bs)
> +{
> +    BDRVQcow2State *s = bs->opaque;
> +    int ret;
> +
> +    if (!s->zoned_header.max_open_zones) {
> +        return 0;
> +    }
> +
> +    if (s->nr_zones_exp_open + s->nr_zones_imp_open
> +        < s->zoned_header.max_open_zones) {
> +        return 0;
> +    }
> +
> +    if(s->nr_zones_imp_open && qcow2_check_active_zones(bs)) {
> +        /* TODO: it takes O(n) time complexity (n = nr_zones).
> +         * Optimizations required. */
> +        /* close one implicitly open zones to make it available */
> +        for (int i = s->zoned_header.nr_conv_zones;

Please use uint32_t to keep the types consistent.

> +        i < bs->bl.nr_zones; ++i) {
> +            uint64_t *wp = &bs->wps->wp[i];
> +            if (qcow2_get_zs(*wp) == BLK_ZS_IOPEN) {
> +                ret = qcow2_write_wp_at(bs, wp, i, BLK_ZS_CLOSED);
> +                if (ret < 0) {
> +                    return ret;
> +                }
> +                bs->wps->wp[i] = *wp;

This assignment is unnecessary since wp points to bs->wps->wp[i]. The
value has already been updated.

> +                s->nr_zones_imp_open--;
> +                s->nr_zones_closed++;
> +                break;
> +            }
> +        }
> +        return 0;

If this for loop completes with i == bs->bl.nr_zones then we've failed
to close an IOPEN zone. The return value should be an error like -EBUSY.

> +    }
> +
> +    return -EINVAL;

Which case does this -EINVAL cover? Won't we get here when no zones are
open yet?

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 2/4] qcow2: add configurations for zoned format extension
  2023-09-01 11:07   ` Markus Armbruster
@ 2023-09-18  8:24     ` Sam Li
  2023-09-18 14:46       ` Markus Armbruster
  0 siblings, 1 reply; 13+ messages in thread
From: Sam Li @ 2023-09-18  8:24 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Kevin Wolf, dmitry.fomichev, Hanna Reitz, Eric Blake,
	hare, qemu-block, stefanha, dlemoal

Markus Armbruster <armbru@redhat.com> 于2023年9月1日周五 19:08写道:
>
> Sam Li <faithilikerun@gmail.com> writes:
>
> > To configure the zoned format feature on the qcow2 driver, it
> > requires following arguments: the device size, zoned profile,
>
> "Zoned profile" is gone in v3.
>
> > zone model, zone size, zone capacity, number of conventional
> > zones, limits on zone resources (max append sectors, max open
> > zones, and max_active_zones).
> >
> > To create a qcow2 file with zoned format, use command like this:
> > $ qemu-img create -f qcow2 test.qcow2 -o size=768M -o
> > zone_size=64M -o zone_capacity=64M -o nr_conv_zones=0 -o
> > max_append_sectors=512 -o max_open_zones=0 -o max_active_zones=0
> > -o zone_model=1
> >
> > Signed-off-by: Sam Li <faithilikerun@gmail.com>
>
> [...]
>
> > diff --git a/qapi/block-core.json b/qapi/block-core.json
> > index 2b1d493d6e..0d8f9e0a88 100644
> > --- a/qapi/block-core.json
> > +++ b/qapi/block-core.json
> > @@ -5021,6 +5021,27 @@
> >  # @compression-type: The image cluster compression method
> >  #     (default: zlib, since 5.1)
> >  #
> > +# @zone-model: Zoned device model, 1 for host-managed and 0 for
>
> Why is this encoded as a number?
>
> If it's fundamentally a flag, use bool.
>
> If more models could appear in the future, make it an enum.
>

Yes, it is an enum.

typedef enum BlockZoneModel {
    BLK_Z_NONE = 0x0, /* Regular block device */
    BLK_Z_HM = 0x1, /* Host-managed zoned block device */
    BLK_Z_HA = 0x2, /* Host-aware zoned block device */
} BlockZoneModel;

> > +#     non-zoned devices (default: 0, since 8.0)
>
> Since 8.2.  More of the same below.
>
> > +#
> > +# @zone-size: Total number of logical blocks within zones in bytes
> > +#     (since 8.0)
> > +#
> > +# @zone-capacity: The number of usable logical blocks within zones
> > +#     in bytes. A zone capacity is always smaller or equal to the
> > +#     zone size. (since 8.0)
>
> Two spaces between sentences for consistency, please.
>
> > +#
> > +# @nr-conv-zones: The number of conventional zones of the zoned device
> > +#     (since 8.0)
>
> I think @conventional-zones would be more obvious.
>
> > +#
> > +# @max-open-zones: The maximal allowed open zones (since 8.0)
>
> Maybe "The maximum number of open zones".
>
> > +#
> > +# @max-active-zones: The limit of the zones that have the implicit
> > +#     open, explicit open or closed state (since 8.0)
>
> Maybe "The maximum number of zones in the implicit open, explicit open
> or closed state".
>
> > +#
> > +# @max-append-sectors: The maximal data size in sectors of a zone
> > +#     append request that can be issued to the device. (since 8.0)
>
> What's the sector size, and how can the user determine it?  Why can't we
> use bytes here?

The sector size is 512 bytes. It's more for conventional use.

>
> > +#
> >  # Since: 2.12
> >  ##
> >  { 'struct': 'BlockdevCreateOptionsQcow2',
> > @@ -5037,7 +5058,14 @@
> >              '*preallocation':   'PreallocMode',
> >              '*lazy-refcounts':  'bool',
> >              '*refcount-bits':   'int',
> > -            '*compression-type':'Qcow2CompressionType' } }
> > +            '*compression-type':'Qcow2CompressionType',
> > +            '*zone-model':         'uint8',
> > +            '*zone-size':          'size',
> > +            '*zone-capacity':      'size',
> > +            '*nr-conv-zones':      'uint32',
> > +            '*max-open-zones':     'uint32',
> > +            '*max-active-zones':   'uint32',
> > +            '*max-append-sectors': 'uint32' } }
> >
> >  ##
> >  # @BlockdevCreateOptionsQed:
>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 2/4] qcow2: add configurations for zoned format extension
  2023-09-13 20:12   ` Stefan Hajnoczi
@ 2023-09-18  8:55     ` Sam Li
  0 siblings, 0 replies; 13+ messages in thread
From: Sam Li @ 2023-09-18  8:55 UTC (permalink / raw)
  To: Stefan Hajnoczi
  Cc: qemu-devel, Kevin Wolf, dmitry.fomichev, Hanna Reitz,
	Markus Armbruster, Eric Blake, hare, qemu-block, dlemoal

Stefan Hajnoczi <stefanha@redhat.com> 于2023年9月14日周四 04:12写道:
>
> On Mon, Aug 28, 2023 at 11:09:53PM +0800, Sam Li wrote:
> > To configure the zoned format feature on the qcow2 driver, it
> > requires following arguments: the device size, zoned profile,
> > zone model, zone size, zone capacity, number of conventional
> > zones, limits on zone resources (max append sectors, max open
> > zones, and max_active_zones).
> >
> > To create a qcow2 file with zoned format, use command like this:
> > $ qemu-img create -f qcow2 test.qcow2 -o size=768M -o
> > zone_size=64M -o zone_capacity=64M -o nr_conv_zones=0 -o
> > max_append_sectors=512 -o max_open_zones=0 -o max_active_zones=0
> > -o zone_model=1
> >
> > Signed-off-by: Sam Li <faithilikerun@gmail.com>
> > ---
> >  block/qcow2.c                    | 176 ++++++++++++++++++++++++++++++-
> >  block/qcow2.h                    |  20 ++++
> >  docs/interop/qcow2.txt           |  36 +++++++
> >  include/block/block_int-common.h |  13 +++
> >  qapi/block-core.json             |  30 +++++-
> >  5 files changed, 273 insertions(+), 2 deletions(-)
> >
> > diff --git a/block/qcow2.c b/block/qcow2.c
> > index c51388e99d..7074bfc620 100644
> > --- a/block/qcow2.c
> > +++ b/block/qcow2.c
> > @@ -73,6 +73,7 @@ typedef struct {
> >  #define  QCOW2_EXT_MAGIC_CRYPTO_HEADER 0x0537be77
> >  #define  QCOW2_EXT_MAGIC_BITMAPS 0x23852875
> >  #define  QCOW2_EXT_MAGIC_DATA_FILE 0x44415441
> > +#define  QCOW2_EXT_MAGIC_ZONED_FORMAT 0x7a6264
> >
> >  static int coroutine_fn
> >  qcow2_co_preadv_compressed(BlockDriverState *bs,
> > @@ -210,6 +211,7 @@ qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
> >      uint64_t offset;
> >      int ret;
> >      Qcow2BitmapHeaderExt bitmaps_ext;
> > +    Qcow2ZonedHeaderExtension zoned_ext;
> >
> >      if (need_update_header != NULL) {
> >          *need_update_header = false;
> > @@ -431,6 +433,55 @@ qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset,
> >              break;
> >          }
> >
> > +        case QCOW2_EXT_MAGIC_ZONED_FORMAT:
> > +        {
> > +            if (ext.len != sizeof(zoned_ext)) {
> > +                error_setg(errp, "zoned_ext: Invalid extension length");
> > +                return -EINVAL;
> > +            }
> > +            ret = bdrv_pread(bs->file, offset, ext.len, &zoned_ext, 0);
> > +            if (ret < 0) {
> > +                error_setg_errno(errp, -ret, "zoned_ext: "
> > +                                             "Could not read ext header");
> > +                return ret;
> > +            }
> > +
> > +            zoned_ext.zone_size = be32_to_cpu(zoned_ext.zone_size);
> > +            zoned_ext.zone_capacity = be32_to_cpu(zoned_ext.zone_capacity);
> > +            zoned_ext.nr_conv_zones = be32_to_cpu(zoned_ext.nr_conv_zones);
> > +            zoned_ext.nr_zones = be32_to_cpu(zoned_ext.nr_zones);
> > +            zoned_ext.max_open_zones = be32_to_cpu(zoned_ext.max_open_zones);
> > +            zoned_ext.max_active_zones =
> > +                be32_to_cpu(zoned_ext.max_active_zones);
> > +            zoned_ext.max_append_sectors =
> > +                be32_to_cpu(zoned_ext.max_append_sectors);
> > +            s->zoned_header = zoned_ext;
> > +
> > +            /* refuse to open broken images */
> > +            if (zoned_ext.zone_size == 0) {
> > +                error_setg(errp, "Zoned extension header zone_size field "
> > +                                 "can not be 0");
> > +                return -EINVAL;
> > +            }
> > +            if (zoned_ext.zone_capacity > zoned_ext.zone_size) {
> > +                error_setg(errp, "Zoned extension header zone_capacity field "
> > +                                 "can not be larger that zone_size field");
> > +                return -EINVAL;
> > +            }
> > +            if (zoned_ext.nr_zones != DIV_ROUND_UP(
> > +                bs->total_sectors * BDRV_SECTOR_SIZE, zoned_ext.zone_size)) {
> > +                error_setg(errp, "Zoned extension header nr_zones field "
> > +                                 "gets wrong");
>
> "gets" -> "is"
>
> > +                return -EINVAL;
> > +            }
> > +
> > +#ifdef DEBUG_EXT
> > +            printf("Qcow2: Got zoned format extension: "
> > +                   "offset=%" PRIu32 "\n", offset);
> > +#endif
> > +            break;
> > +        }
> > +
> >          default:
> >              /* unknown magic - save it in case we need to rewrite the header */
> >              /* If you add a new feature, make sure to also update the fast
> > @@ -1967,6 +2018,14 @@ static void qcow2_refresh_limits(BlockDriverState *bs, Error **errp)
> >      }
> >      bs->bl.pwrite_zeroes_alignment = s->subcluster_size;
> >      bs->bl.pdiscard_alignment = s->cluster_size;
> > +    bs->bl.zoned = s->zoned_header.zoned;
> > +    bs->bl.nr_zones = s->zoned_header.nr_zones;
> > +    bs->wps = s->wps;
> > +    bs->bl.max_append_sectors = s->zoned_header.max_append_sectors;
> > +    bs->bl.max_active_zones = s->zoned_header.max_active_zones;
> > +    bs->bl.max_open_zones = s->zoned_header.max_open_zones;
> > +    bs->bl.zone_size = s->zoned_header.zone_size;
> > +    bs->bl.write_granularity = BDRV_SECTOR_SIZE;
> >  }
> >
> >  static int qcow2_reopen_prepare(BDRVReopenState *state,
> > @@ -3089,6 +3148,30 @@ int qcow2_update_header(BlockDriverState *bs)
> >          buflen -= ret;
> >      }
> >
> > +    /* Zoned devices header extension */
> > +    if (s->zoned_header.zoned == BLK_Z_HM) {
> > +        Qcow2ZonedHeaderExtension zoned_header = {
> > +            .zoned              = s->zoned_header.zoned,
> > +            .zone_size          = cpu_to_be32(s->zoned_header.zone_size),
> > +            .zone_capacity      = cpu_to_be32(s->zoned_header.zone_capacity),
> > +            .nr_conv_zones      = cpu_to_be32(s->zoned_header.nr_conv_zones),
> > +            .nr_zones           = cpu_to_be32(s->zoned_header.nr_zones),
> > +            .max_open_zones     = cpu_to_be32(s->zoned_header.max_open_zones),
> > +            .max_active_zones   =
> > +                cpu_to_be32(s->zoned_header.max_active_zones),
> > +            .max_append_sectors =
> > +                cpu_to_be32(s->zoned_header.max_append_sectors)
> > +        };
> > +        ret = header_ext_add(buf, QCOW2_EXT_MAGIC_ZONED_FORMAT,
> > +                             &zoned_header, sizeof(zoned_header),
> > +                             buflen);
> > +        if (ret < 0) {
> > +            goto fail;
> > +        }
> > +        buf += ret;
> > +        buflen -= ret;
> > +    }
> > +
> >      /* Keep unknown header extensions */
> >      QLIST_FOREACH(uext, &s->unknown_header_ext, next) {
> >          ret = header_ext_add(buf, uext->magic, uext->data, uext->len, buflen);
> > @@ -3768,11 +3851,60 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
> >      }
> >
> >      /* Set the external data file if necessary */
> > +    BDRVQcow2State *s = blk_bs(blk)->opaque;
> >      if (data_bs) {
> > -        BDRVQcow2State *s = blk_bs(blk)->opaque;
> >          s->image_data_file = g_strdup(data_bs->filename);
> >      }
> >
> > +    if (qcow2_opts->has_zone_model && qcow2_opts->zone_model == BLK_Z_HM) {
> > +        if (qcow2_opts->has_zone_size && qcow2_opts->zone_size == 0) {
>
> has_zone_size is mandatory:
>
>   if (!qcow2_opts->has_zone_size) {
>       error_setg(errp, "Missing zone_size parameter");
>       ret = -EINVAL;
>       goto out;
>   }
>   ...
>
> > +            s->zoned_header.zoned = BLK_Z_NONE;
> > +            error_setg(errp, "Zoned devices can not allow a larger-than-zero "
> > +                             "zone_size");
>
> Missing "ret = -EINVAL;".
>
> > +            goto out;
> > +        }
> > +        s->zoned_header.zoned = qcow2_opts->zone_model;
> > +        s->zoned_header.zone_size = qcow2_opts->zone_size;
> > +        s->zoned_header.nr_zones = DIV_ROUND_UP(qcow2_opts->size,
> > +                                                qcow2_opts->zone_size);
> > +
> > +        if (qcow2_opts->has_zone_capacity) {
> > +            if (qcow2_opts->zone_capacity > qcow2_opts->zone_size) {
> > +                s->zoned_header.zoned = BLK_Z_NONE;
> > +                error_setg(errp, "zone capacity %" PRIu64 "B exceeds zone size "
> > +                           "%" PRIu64"B", qcow2_opts->zone_capacity,
> > +                           qcow2_opts->zone_size);
>
> Missing "ret = -EINVAL;".
>
> > +                goto out;
> > +            }
> > +            s->zoned_header.zone_capacity = qcow2_opts->zone_capacity;
> > +        } else {
> > +            s->zoned_header.zone_capacity = qcow2_opts->zone_size;
> > +        }
> > +
> > +        if (qcow2_opts->has_nr_conv_zones) {
> > +            s->zoned_header.nr_conv_zones = qcow2_opts->nr_conv_zones;
> > +        }
> > +
> > +        if (qcow2_opts->has_max_active_zones) {
> > +            if (qcow2_opts->max_open_zones > qcow2_opts->max_active_zones) {
> > +                s->zoned_header.zoned = BLK_Z_NONE;
> > +                error_setg(errp, "max_open_zones %" PRIu32 " exceeds "
> > +                           "max_active_zones %" PRIu32"",
> > +                           qcow2_opts->max_open_zones,
> > +                           qcow2_opts->max_active_zones);
>
> Missing "ret = -EINVAL;".
>
> > +                goto out;
> > +            }
> > +            if (qcow2_opts->has_max_open_zones) {
> > +                s->zoned_header.max_open_zones = qcow2_opts->max_active_zones;
> > +            } else {
> > +                s->zoned_header.max_open_zones = qcow2_opts->max_active_zones;
> > +            }
> > +        }
> > +        s->zoned_header.max_append_sectors = qcow2_opts->max_append_sectors;
> > +    } else {
> > +        s->zoned_header.zoned = BLK_Z_NONE;
> > +    }
> > +
> >      /* Create a full header (including things like feature table) */
> >      ret = qcow2_update_header(blk_bs(blk));
> >      bdrv_graph_co_rdunlock();
> > @@ -3903,6 +4035,13 @@ qcow2_co_create_opts(BlockDriver *drv, const char *filename, QemuOpts *opts,
> >          { BLOCK_OPT_COMPAT_LEVEL,       "version" },
> >          { BLOCK_OPT_DATA_FILE_RAW,      "data-file-raw" },
> >          { BLOCK_OPT_COMPRESSION_TYPE,   "compression-type" },
> > +        { BLOCK_OPT_Z_MODEL,            "zone-model"},
> > +        { BLOCK_OPT_Z_NR_COV,           "nr-conv-zones"},
> > +        { BLOCK_OPT_Z_MOZ,              "max-open-zones"},
> > +        { BLOCK_OPT_Z_MAZ,              "max-active-zones"},
> > +        { BLOCK_OPT_Z_MAS,              "max-append-sectors"},
> > +        { BLOCK_OPT_Z_SIZE,             "zone-size"},
> > +        { BLOCK_OPT_Z_CAP,              "zone-capacity"},
> >          { NULL, NULL },
> >      };
> >
> > @@ -6066,6 +6205,41 @@ static QemuOptsList qcow2_create_opts = {
> >              .help = "Compression method used for image cluster "        \
> >                      "compression",                                      \
> >              .def_value_str = "zlib"                                     \
> > +        },                                                              \
> > +        {                                                               \
> > +            .name = BLOCK_OPT_Z_MODEL,                                  \
> > +            .type = QEMU_OPT_NUMBER,                                    \
> > +            .help = "zone model",                                      \
> > +        },                                                              \
> > +        {                                                               \
> > +            .name = BLOCK_OPT_Z_SIZE,                                   \
> > +            .type = QEMU_OPT_SIZE,                                      \
> > +            .help = "zone size",                                        \
> > +        },                                                              \
> > +        {                                                               \
> > +            .name = BLOCK_OPT_Z_CAP,                                    \
> > +            .type = QEMU_OPT_SIZE,                                      \
> > +            .help = "zone capacity",                                    \
> > +        },                                                              \
> > +        {                                                               \
> > +            .name = BLOCK_OPT_Z_NR_COV,                                 \
> > +            .type = QEMU_OPT_NUMBER,                                    \
> > +            .help = "numbers of conventional zones",                    \
> > +        },                                                              \
> > +        {                                                               \
> > +            .name = BLOCK_OPT_Z_MAS,                                    \
> > +            .type = QEMU_OPT_NUMBER,                                    \
> > +            .help = "max append sectors",                               \
> > +        },                                                              \
> > +        {                                                               \
> > +            .name = BLOCK_OPT_Z_MAZ,                                    \
> > +            .type = QEMU_OPT_NUMBER,                                    \
> > +            .help = "max active zones",                                 \
> > +        },                                                              \
> > +        {                                                               \
> > +            .name = BLOCK_OPT_Z_MOZ,                                    \
> > +            .type = QEMU_OPT_NUMBER,                                    \
> > +            .help = "max open zones",                                   \
> >          },
> >          QCOW_COMMON_OPTIONS,
> >          { /* end of list */ }
> > diff --git a/block/qcow2.h b/block/qcow2.h
> > index f789ce3ae0..edb8eebcb3 100644
> > --- a/block/qcow2.h
> > +++ b/block/qcow2.h
> > @@ -236,6 +236,19 @@ typedef struct Qcow2CryptoHeaderExtension {
> >      uint64_t length;
> >  } QEMU_PACKED Qcow2CryptoHeaderExtension;
> >
> > +typedef struct Qcow2ZonedHeaderExtension {
> > +    /* Zoned device attributes */
> > +    uint8_t zoned;
> > +    uint8_t reserved[3];
> > +    uint32_t zone_size;
> > +    uint32_t zone_capacity;
> > +    uint32_t nr_conv_zones;
> > +    uint32_t nr_zones;
> > +    uint32_t max_active_zones;
> > +    uint32_t max_open_zones;
> > +    uint32_t max_append_sectors;
> > +} QEMU_PACKED Qcow2ZonedHeaderExtension;
> > +
> >  typedef struct Qcow2UnknownHeaderExtension {
> >      uint32_t magic;
> >      uint32_t len;
> > @@ -422,6 +435,13 @@ typedef struct BDRVQcow2State {
> >       * is to convert the image with the desired compression type set.
> >       */
> >      Qcow2CompressionType compression_type;
> > +
> > +    /* States of zoned device */
> > +    Qcow2ZonedHeaderExtension zoned_header;
> > +    uint32_t nr_zones_exp_open;
> > +    uint32_t nr_zones_imp_open;
> > +    uint32_t nr_zones_closed;
> > +    BlockZoneWps *wps;
>
> How does this relate to bs->wps? I think I've asked about this before.
> I wonder if it's possible to avoid duplicating this field in two
> structs.

This field is used for storing write pointers in the metadata.
"bs->wps" reads/writes wp from/to this field. I can only remove
"s->wps" in BDRVQcow2State while maintaining wp tracking.

219:    zone_wp = bs->wps->wp[index];
343:    memcpy(bs->wps->wp, temp, wps_size);
612:            bs->wps = g_malloc(sizeof(BlockZoneWps)
629:            qemu_co_mutex_init(&bs->wps->colock);

>
> >  } BDRVQcow2State;
> >
> >  typedef struct Qcow2COWRegion {
> > diff --git a/docs/interop/qcow2.txt b/docs/interop/qcow2.txt
> > index 2c4618375a..80ff03a826 100644
> > --- a/docs/interop/qcow2.txt
> > +++ b/docs/interop/qcow2.txt
> > @@ -331,6 +331,42 @@ The fields of the bitmaps extension are:
> >                     Offset into the image file at which the bitmap directory
> >                     starts. Must be aligned to a cluster boundary.
> >
> > +== Zoned extension ==
> > +
> > +The zoned extension is an optional header extension. It contains fields for
> > +emulating the zoned stroage model (https://zonedstorage.io/).
> > +
> > +The fields of the zoned extension are:
> > +    Byte        0:  zoned
> > +                    Zoned model, 1 for host-managed and 0 for non-zoned devices.
> > +
> > +            1 - 3:  Reserved, must be zero.
> > +
> > +            4 - 7:  zone_size
> > +                    Total number of logical blocks within the zones in bytes.
> > +
> > +           8 - 11:  zone_capacity
> > +                    The number of writable logical blocks within the zones in
> > +                    bytes. A zone capacity is always smaller or equal to the
> > +                    zone size.
> > +
> > +          12 - 15:  nr_conv_zones
> > +                    The number of conventional zones.
> > +
> > +          16 - 19:  nr_zones
> > +                    The number of zones.
> > +
> > +          20 - 23:  max_active_zones
> > +                    The limit of the zones that have the implicit open,
> > +                    explicit open or closed state.
> > +
> > +          24 - 27:  max_open_zones
> > +                    The maximal allowed open zones.
> > +
> > +          28 - 35:  max_append_sectors
> > +                    The maximal data size in sectors of a zone
>
> 512-byte sectors? Please clarify the size of the sectors.
>
> > +                    append request that can be issued to the device.
> > +
> >  == Full disk encryption header pointer ==
> >
> >  The full disk encryption header must be present if, and only if, the
> > diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h
> > index 74195c3004..332c0c765c 100644
> > --- a/include/block/block_int-common.h
> > +++ b/include/block/block_int-common.h
> > @@ -57,6 +57,13 @@
> >  #define BLOCK_OPT_DATA_FILE_RAW     "data_file_raw"
> >  #define BLOCK_OPT_COMPRESSION_TYPE  "compression_type"
> >  #define BLOCK_OPT_EXTL2             "extended_l2"
> > +#define BLOCK_OPT_Z_MODEL           "zone_model"
> > +#define BLOCK_OPT_Z_SIZE            "zone_size"
> > +#define BLOCK_OPT_Z_CAP             "zone_capacity"
> > +#define BLOCK_OPT_Z_NR_COV          "nr_conv_zones"
> > +#define BLOCK_OPT_Z_MAS             "max_append_sectors"
> > +#define BLOCK_OPT_Z_MAZ             "max_active_zones"
> > +#define BLOCK_OPT_Z_MOZ             "max_open_zones"
> >
> >  #define BLOCK_PROBE_BUF_SIZE        512
> >
> > @@ -878,6 +885,12 @@ typedef struct BlockLimits {
> >      /* zone size expressed in bytes */
> >      uint32_t zone_size;
> >
> > +    /*
> > +     * the number of usable logical blocks within the zone, expressed
> > +     * in bytes. A zone capacity is smaller or equal to the zone size.
> > +     */
> > +    uint32_t zone_capacity;
> > +
> >      /* total number of zones */
> >      uint32_t nr_zones;
> >
> > diff --git a/qapi/block-core.json b/qapi/block-core.json
> > index 2b1d493d6e..0d8f9e0a88 100644
> > --- a/qapi/block-core.json
> > +++ b/qapi/block-core.json
> > @@ -5021,6 +5021,27 @@
> >  # @compression-type: The image cluster compression method
> >  #     (default: zlib, since 5.1)
> >  #
> > +# @zone-model: Zoned device model, 1 for host-managed and 0 for
> > +#     non-zoned devices (default: 0, since 8.0)
> > +#
> > +# @zone-size: Total number of logical blocks within zones in bytes
> > +#     (since 8.0)
> > +#
> > +# @zone-capacity: The number of usable logical blocks within zones
> > +#     in bytes. A zone capacity is always smaller or equal to the
> > +#     zone size. (since 8.0)
> > +#
> > +# @nr-conv-zones: The number of conventional zones of the zoned device
> > +#     (since 8.0)
> > +#
> > +# @max-open-zones: The maximal allowed open zones (since 8.0)
> > +#
> > +# @max-active-zones: The limit of the zones that have the implicit
> > +#     open, explicit open or closed state (since 8.0)
> > +#
> > +# @max-append-sectors: The maximal data size in sectors of a zone
> > +#     append request that can be issued to the device. (since 8.0)
> > +#
> >  # Since: 2.12
> >  ##
> >  { 'struct': 'BlockdevCreateOptionsQcow2',
> > @@ -5037,7 +5058,14 @@
> >              '*preallocation':   'PreallocMode',
> >              '*lazy-refcounts':  'bool',
> >              '*refcount-bits':   'int',
> > -            '*compression-type':'Qcow2CompressionType' } }
> > +            '*compression-type':'Qcow2CompressionType',
> > +            '*zone-model':         'uint8',
> > +            '*zone-size':          'size',
> > +            '*zone-capacity':      'size',
> > +            '*nr-conv-zones':      'uint32',
> > +            '*max-open-zones':     'uint32',
> > +            '*max-active-zones':   'uint32',
> > +            '*max-append-sectors': 'uint32' } }
> >
> >  ##
> >  # @BlockdevCreateOptionsQed:
> > --
> > 2.40.1
> >


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 2/4] qcow2: add configurations for zoned format extension
  2023-09-18  8:24     ` Sam Li
@ 2023-09-18 14:46       ` Markus Armbruster
  2023-09-18 14:52         ` Sam Li
  0 siblings, 1 reply; 13+ messages in thread
From: Markus Armbruster @ 2023-09-18 14:46 UTC (permalink / raw)
  To: Sam Li
  Cc: Markus Armbruster, qemu-devel, Kevin Wolf, dmitry.fomichev,
	Hanna Reitz, Eric Blake, hare, qemu-block, stefanha, dlemoal

Sam Li <faithilikerun@gmail.com> writes:

> Markus Armbruster <armbru@redhat.com> 于2023年9月1日周五 19:08写道:
>>
>> Sam Li <faithilikerun@gmail.com> writes:
>>
>> > To configure the zoned format feature on the qcow2 driver, it
>> > requires following arguments: the device size, zoned profile,
>>
>> "Zoned profile" is gone in v3.
>>
>> > zone model, zone size, zone capacity, number of conventional
>> > zones, limits on zone resources (max append sectors, max open
>> > zones, and max_active_zones).
>> >
>> > To create a qcow2 file with zoned format, use command like this:
>> > $ qemu-img create -f qcow2 test.qcow2 -o size=768M -o
>> > zone_size=64M -o zone_capacity=64M -o nr_conv_zones=0 -o
>> > max_append_sectors=512 -o max_open_zones=0 -o max_active_zones=0
>> > -o zone_model=1
>> >
>> > Signed-off-by: Sam Li <faithilikerun@gmail.com>
>>
>> [...]
>>
>> > diff --git a/qapi/block-core.json b/qapi/block-core.json
>> > index 2b1d493d6e..0d8f9e0a88 100644
>> > --- a/qapi/block-core.json
>> > +++ b/qapi/block-core.json
>> > @@ -5021,6 +5021,27 @@
>> >  # @compression-type: The image cluster compression method
>> >  #     (default: zlib, since 5.1)
>> >  #
>> > +# @zone-model: Zoned device model, 1 for host-managed and 0 for
>>
>> Why is this encoded as a number?
>>
>> If it's fundamentally a flag, use bool.
>>
>> If more models could appear in the future, make it an enum.
>>
>
> Yes, it is an enum.
>
> typedef enum BlockZoneModel {
>     BLK_Z_NONE = 0x0, /* Regular block device */
>     BLK_Z_HM = 0x1, /* Host-managed zoned block device */
>     BLK_Z_HA = 0x2, /* Host-aware zoned block device */
> } BlockZoneModel;

Please make it an enum in the QAPI schema, too.

>> > +#     non-zoned devices (default: 0, since 8.0)
>>
>> Since 8.2.  More of the same below.
>>
>> > +#
>> > +# @zone-size: Total number of logical blocks within zones in bytes
>> > +#     (since 8.0)
>> > +#
>> > +# @zone-capacity: The number of usable logical blocks within zones
>> > +#     in bytes. A zone capacity is always smaller or equal to the
>> > +#     zone size. (since 8.0)
>>
>> Two spaces between sentences for consistency, please.
>>
>> > +#
>> > +# @nr-conv-zones: The number of conventional zones of the zoned device
>> > +#     (since 8.0)
>>
>> I think @conventional-zones would be more obvious.
>>
>> > +#
>> > +# @max-open-zones: The maximal allowed open zones (since 8.0)
>>
>> Maybe "The maximum number of open zones".
>>
>> > +#
>> > +# @max-active-zones: The limit of the zones that have the implicit
>> > +#     open, explicit open or closed state (since 8.0)
>>
>> Maybe "The maximum number of zones in the implicit open, explicit open
>> or closed state".
>>
>> > +#
>> > +# @max-append-sectors: The maximal data size in sectors of a zone
>> > +#     append request that can be issued to the device. (since 8.0)
>>
>> What's the sector size, and how can the user determine it?  Why can't we
>> use bytes here?
>
> The sector size is 512 bytes.

Needs to be documented.

I believe bytes would be easier to document, which makes me suspect
they'd be the simpler interface.

>                               It's more for conventional use.

I'm afraid I don't understand this part.  Do I have to?

>> > +#
>> >  # Since: 2.12
>> >  ##
>> >  { 'struct': 'BlockdevCreateOptionsQcow2',
>> > @@ -5037,7 +5058,14 @@
>> >              '*preallocation':   'PreallocMode',
>> >              '*lazy-refcounts':  'bool',
>> >              '*refcount-bits':   'int',
>> > -            '*compression-type':'Qcow2CompressionType' } }
>> > +            '*compression-type':'Qcow2CompressionType',
>> > +            '*zone-model':         'uint8',
>> > +            '*zone-size':          'size',
>> > +            '*zone-capacity':      'size',
>> > +            '*nr-conv-zones':      'uint32',
>> > +            '*max-open-zones':     'uint32',
>> > +            '*max-active-zones':   'uint32',
>> > +            '*max-append-sectors': 'uint32' } }
>> >
>> >  ##
>> >  # @BlockdevCreateOptionsQed:
>>



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v3 2/4] qcow2: add configurations for zoned format extension
  2023-09-18 14:46       ` Markus Armbruster
@ 2023-09-18 14:52         ` Sam Li
  0 siblings, 0 replies; 13+ messages in thread
From: Sam Li @ 2023-09-18 14:52 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: qemu-devel, Kevin Wolf, dmitry.fomichev, Hanna Reitz, Eric Blake,
	hare, qemu-block, stefanha, dlemoal

Markus Armbruster <armbru@redhat.com> 于2023年9月18日周一 22:46写道:
>
> Sam Li <faithilikerun@gmail.com> writes:
>
> > Markus Armbruster <armbru@redhat.com> 于2023年9月1日周五 19:08写道:
> >>
> >> Sam Li <faithilikerun@gmail.com> writes:
> >>
> >> > To configure the zoned format feature on the qcow2 driver, it
> >> > requires following arguments: the device size, zoned profile,
> >>
> >> "Zoned profile" is gone in v3.
> >>
> >> > zone model, zone size, zone capacity, number of conventional
> >> > zones, limits on zone resources (max append sectors, max open
> >> > zones, and max_active_zones).
> >> >
> >> > To create a qcow2 file with zoned format, use command like this:
> >> > $ qemu-img create -f qcow2 test.qcow2 -o size=768M -o
> >> > zone_size=64M -o zone_capacity=64M -o nr_conv_zones=0 -o
> >> > max_append_sectors=512 -o max_open_zones=0 -o max_active_zones=0
> >> > -o zone_model=1
> >> >
> >> > Signed-off-by: Sam Li <faithilikerun@gmail.com>
> >>
> >> [...]
> >>
> >> > diff --git a/qapi/block-core.json b/qapi/block-core.json
> >> > index 2b1d493d6e..0d8f9e0a88 100644
> >> > --- a/qapi/block-core.json
> >> > +++ b/qapi/block-core.json
> >> > @@ -5021,6 +5021,27 @@
> >> >  # @compression-type: The image cluster compression method
> >> >  #     (default: zlib, since 5.1)
> >> >  #
> >> > +# @zone-model: Zoned device model, 1 for host-managed and 0 for
> >>
> >> Why is this encoded as a number?
> >>
> >> If it's fundamentally a flag, use bool.
> >>
> >> If more models could appear in the future, make it an enum.
> >>
> >
> > Yes, it is an enum.
> >
> > typedef enum BlockZoneModel {
> >     BLK_Z_NONE = 0x0, /* Regular block device */
> >     BLK_Z_HM = 0x1, /* Host-managed zoned block device */
> >     BLK_Z_HA = 0x2, /* Host-aware zoned block device */
> > } BlockZoneModel;
>
> Please make it an enum in the QAPI schema, too.

I see.

>
> >> > +#     non-zoned devices (default: 0, since 8.0)
> >>
> >> Since 8.2.  More of the same below.
> >>
> >> > +#
> >> > +# @zone-size: Total number of logical blocks within zones in bytes
> >> > +#     (since 8.0)
> >> > +#
> >> > +# @zone-capacity: The number of usable logical blocks within zones
> >> > +#     in bytes. A zone capacity is always smaller or equal to the
> >> > +#     zone size. (since 8.0)
> >>
> >> Two spaces between sentences for consistency, please.
> >>
> >> > +#
> >> > +# @nr-conv-zones: The number of conventional zones of the zoned device
> >> > +#     (since 8.0)
> >>
> >> I think @conventional-zones would be more obvious.
> >>
> >> > +#
> >> > +# @max-open-zones: The maximal allowed open zones (since 8.0)
> >>
> >> Maybe "The maximum number of open zones".
> >>
> >> > +#
> >> > +# @max-active-zones: The limit of the zones that have the implicit
> >> > +#     open, explicit open or closed state (since 8.0)
> >>
> >> Maybe "The maximum number of zones in the implicit open, explicit open
> >> or closed state".
> >>
> >> > +#
> >> > +# @max-append-sectors: The maximal data size in sectors of a zone
> >> > +#     append request that can be issued to the device. (since 8.0)
> >>
> >> What's the sector size, and how can the user determine it?  Why can't we
> >> use bytes here?
> >
> > The sector size is 512 bytes.
>
> Needs to be documented.
>
> I believe bytes would be easier to document, which makes me suspect
> they'd be the simpler interface.
>
> >                               It's more for conventional use.
>
> I'm afraid I don't understand this part.  Do I have to?

Not necessarily. I adopt the name from zoned storage part of virtio spec.

+If the VIRTIO_BLK_F_ZONED feature is negotiated, then in
+\field{virtio_blk_zoned_characteristics},
+\begin{itemize}
+\item \field{zone_sectors} value is expressed in 512-byte sectors.
+\item \field{max_append_sectors} value is expressed in 512-byte sectors.
+\item \field{write_granularity} value is expressed in bytes.
+\end{itemize}

>
> >> > +#
> >> >  # Since: 2.12
> >> >  ##
> >> >  { 'struct': 'BlockdevCreateOptionsQcow2',
> >> > @@ -5037,7 +5058,14 @@
> >> >              '*preallocation':   'PreallocMode',
> >> >              '*lazy-refcounts':  'bool',
> >> >              '*refcount-bits':   'int',
> >> > -            '*compression-type':'Qcow2CompressionType' } }
> >> > +            '*compression-type':'Qcow2CompressionType',
> >> > +            '*zone-model':         'uint8',
> >> > +            '*zone-size':          'size',
> >> > +            '*zone-capacity':      'size',
> >> > +            '*nr-conv-zones':      'uint32',
> >> > +            '*max-open-zones':     'uint32',
> >> > +            '*max-active-zones':   'uint32',
> >> > +            '*max-append-sectors': 'uint32' } }
> >> >
> >> >  ##
> >> >  # @BlockdevCreateOptionsQed:
> >>
>


^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2023-09-18 14:53 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-08-28 15:09 [PATCH v3 0/4] Add full zoned storage emulation to qcow2 driver Sam Li
2023-08-28 15:09 ` [PATCH v3 1/4] docs/qcow2: add the zoned format feature Sam Li
2023-09-06 20:26   ` Stefan Hajnoczi
2023-08-28 15:09 ` [PATCH v3 2/4] qcow2: add configurations for zoned format extension Sam Li
2023-09-01 11:07   ` Markus Armbruster
2023-09-18  8:24     ` Sam Li
2023-09-18 14:46       ` Markus Armbruster
2023-09-18 14:52         ` Sam Li
2023-09-13 20:12   ` Stefan Hajnoczi
2023-09-18  8:55     ` Sam Li
2023-08-28 15:09 ` [PATCH v3 3/4] qcow2: add zoned emulation capability Sam Li
2023-09-13 21:11   ` Stefan Hajnoczi
2023-08-28 15:09 ` [PATCH v3 4/4] iotests: test the zoned format feature for qcow2 file Sam Li

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).