Allow for newly created VDO volumes to select zstd for compression.
Uses geometry version 5.1 for zstd volumes.

Link: 
https://lore.kernel.org/dm-devel/caaijnjogn4jn6o-swaaazwnk4dheicfua0q0x2rldrhh7nr...@mail.gmail.com
Assisted-by: opencode:gpt-5.6-sol
Signed-off-by: Rhajune Park <[email protected]>
---
 drivers/md/dm-vdo/Kconfig         |   2 +
 drivers/md/dm-vdo/Makefile        |   1 +
 drivers/md/dm-vdo/compression.c   | 176 ++++++++++++++++++++++++++++++
 drivers/md/dm-vdo/compression.h   |  27 +++++
 drivers/md/dm-vdo/data-vio.c      |  20 ++--
 drivers/md/dm-vdo/dm-vdo-target.c |  47 +++++++-
 drivers/md/dm-vdo/encodings.c     |  58 +++++++---
 drivers/md/dm-vdo/encodings.h     |  23 +++-
 drivers/md/dm-vdo/message-stats.c |  20 +++-
 drivers/md/dm-vdo/types.h         |   9 ++
 drivers/md/dm-vdo/vdo.c           |  44 ++++----
 drivers/md/dm-vdo/vdo.h           |   6 +-
 12 files changed, 374 insertions(+), 59 deletions(-)
 create mode 100644 drivers/md/dm-vdo/compression.c
 create mode 100644 drivers/md/dm-vdo/compression.h

diff --git a/drivers/md/dm-vdo/Kconfig b/drivers/md/dm-vdo/Kconfig
index 2400b2bc4bc7..eafd82e7af4a 100644
--- a/drivers/md/dm-vdo/Kconfig
+++ b/drivers/md/dm-vdo/Kconfig
@@ -7,6 +7,8 @@ config DM_VDO
        select DM_BUFIO
        select LZ4_COMPRESS
        select LZ4_DECOMPRESS
+       select ZSTD_COMPRESS
+       select ZSTD_DECOMPRESS
        select MIN_HEAP
        help
          This device mapper target presents a block device with
diff --git a/drivers/md/dm-vdo/Makefile b/drivers/md/dm-vdo/Makefile
index 9476957bfbf4..c9cd6ba3c7d7 100644
--- a/drivers/md/dm-vdo/Makefile
+++ b/drivers/md/dm-vdo/Makefile
@@ -9,6 +9,7 @@ dm-vdo-objs := \
        admin-state.o \
        block-map.o \
        completion.o \
+       compression.o \
        data-vio.o \
        dedupe.o \
        dm-vdo-target.o \
diff --git a/drivers/md/dm-vdo/compression.c b/drivers/md/dm-vdo/compression.c
new file mode 100644
index 000000000000..1bb63d4783c0
--- /dev/null
+++ b/drivers/md/dm-vdo/compression.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2026 Rhajune Park <[email protected]>
+ */
+
+#include "compression.h"
+
+#include <linux/lz4.h>
+#include <linux/zstd.h>
+
+#include "constants.h"
+#include "funnel-workqueue.h"
+#include "memory-alloc.h"
+#include "status-codes.h"
+#include "types.h"
+#include "vdo.h"
+
+struct vdo_compression_context {
+       void *compression_workspace;
+       void *decompression_workspace;
+       zstd_cctx *zstd_cctx;
+       zstd_dctx *zstd_dctx;
+       zstd_parameters zstd_parameters;
+};
+
+bool vdo_is_valid_compression_level(int level)
+{
+       return level >= zstd_min_clevel() && level <= zstd_max_clevel();
+}
+
+int vdo_resolve_compression_level(struct device_config *config, char **reason)
+{
+       if (config->compression_algorithm == VDO_COMPRESSION_ALGORITHM_LZ4) {
+               if (config->compression_level_set) {
+                       *reason = "Compression level is only supported with 
ZSTD";
+                       return VDO_BAD_CONFIGURATION;
+               }
+
+               config->compression_level = 0;
+       }
+
+       return VDO_SUCCESS;
+}
+
+static void free_compression_context(struct vdo_compression_context *context)
+{
+       if (context == NULL)
+               return;
+
+       vdo_free(vdo_forget(context->decompression_workspace));
+       vdo_free(vdo_forget(context->compression_workspace));
+       vdo_free(context);
+}
+
+void vdo_free_compression_contexts(struct vdo *vdo)
+{
+       unsigned int i;
+
+       if (vdo->compression_context == NULL)
+               return;
+
+       for (i = 0; i < vdo->device_config->thread_counts.cpu_threads; i++)
+               
free_compression_context(vdo_forget(vdo->compression_context[i]));
+
+       vdo_free(vdo_forget(vdo->compression_context));
+}
+
+static int make_lz4_context(struct vdo_compression_context *context)
+{
+       return vdo_allocate_memory(LZ4_MEM_COMPRESS, 1, "LZ4 context",
+                                  &context->compression_workspace);
+}
+
+static int make_zstd_context(struct vdo_compression_context *context, int 
level)
+{
+       size_t workspace_size;
+       int result;
+
+       context->zstd_parameters = zstd_get_params(level, VDO_BLOCK_SIZE);
+       workspace_size = 
zstd_cctx_workspace_bound(&context->zstd_parameters.cParams);
+       result = vdo_allocate_memory(workspace_size, 8, "ZSTD compression 
context",
+                                    &context->compression_workspace);
+       if (result != VDO_SUCCESS)
+               return result;
+
+       context->zstd_cctx = zstd_init_cctx(context->compression_workspace,
+                                           workspace_size);
+       if (context->zstd_cctx == NULL)
+               return VDO_BAD_CONFIGURATION;
+
+       workspace_size = zstd_dctx_workspace_bound();
+       result = vdo_allocate_memory(workspace_size, 8, "ZSTD decompression 
context",
+                                    &context->decompression_workspace);
+       if (result != VDO_SUCCESS)
+               return result;
+
+       context->zstd_dctx = zstd_init_dctx(context->decompression_workspace,
+                                           workspace_size);
+       return (context->zstd_dctx == NULL) ? VDO_BAD_CONFIGURATION : 
VDO_SUCCESS;
+}
+
+int vdo_make_compression_contexts(struct vdo *vdo)
+{
+       unsigned int i;
+       int result;
+
+       result = vdo_allocate(vdo->device_config->thread_counts.cpu_threads,
+                             "compression contexts", 
&vdo->compression_context);
+       if (result != VDO_SUCCESS)
+               return result;
+
+       for (i = 0; i < vdo->device_config->thread_counts.cpu_threads; i++) {
+               struct vdo_compression_context *context;
+
+               result = vdo_allocate(1, "compression context", &context);
+               if (result != VDO_SUCCESS)
+                       return result;
+
+               vdo->compression_context[i] = context;
+               switch (vdo->geometry.compression_algorithm) {
+               case VDO_COMPRESSION_ALGORITHM_LZ4:
+                       result = make_lz4_context(context);
+                       break;
+               case VDO_COMPRESSION_ALGORITHM_ZSTD:
+                       result = make_zstd_context(context,
+                                                  
vdo->device_config->compression_level);
+                       break;
+               default:
+                       return VDO_BAD_CONFIGURATION;
+               }
+
+               if (result != VDO_SUCCESS)
+                       return result;
+       }
+
+       return VDO_SUCCESS;
+}
+
+size_t vdo_compress_block(struct vdo *vdo, const void *src, void *dst,
+                         size_t dst_size)
+{
+       struct vdo_compression_context *context = 
vdo_get_work_queue_private_data();
+       size_t size;
+
+       switch (vdo->geometry.compression_algorithm) {
+       case VDO_COMPRESSION_ALGORITHM_LZ4:
+               return LZ4_compress_default(src, dst, VDO_BLOCK_SIZE, dst_size,
+                                           context->compression_workspace);
+       case VDO_COMPRESSION_ALGORITHM_ZSTD:
+               size = zstd_compress_cctx(context->zstd_cctx, dst, dst_size, 
src,
+                                         VDO_BLOCK_SIZE, 
&context->zstd_parameters);
+               return zstd_is_error(size) ? 0 : size;
+       default:
+               return 0;
+       }
+}
+
+int vdo_decompress_block(struct vdo *vdo, const void *src, size_t src_size,
+                        void *dst)
+{
+       struct vdo_compression_context *context = 
vdo_get_work_queue_private_data();
+       size_t size;
+
+       switch (vdo->geometry.compression_algorithm) {
+       case VDO_COMPRESSION_ALGORITHM_LZ4:
+               return (LZ4_decompress_safe(src, dst, src_size, VDO_BLOCK_SIZE) 
==
+                       VDO_BLOCK_SIZE) ? VDO_SUCCESS : VDO_INVALID_FRAGMENT;
+       case VDO_COMPRESSION_ALGORITHM_ZSTD:
+               size = zstd_decompress_dctx(context->zstd_dctx, dst, 
VDO_BLOCK_SIZE,
+                                           src, src_size);
+               return (!zstd_is_error(size) && size == VDO_BLOCK_SIZE) ?
+                       VDO_SUCCESS : VDO_INVALID_FRAGMENT;
+       default:
+               return VDO_INVALID_FRAGMENT;
+       }
+}
diff --git a/drivers/md/dm-vdo/compression.h b/drivers/md/dm-vdo/compression.h
new file mode 100644
index 000000000000..74ec1577460e
--- /dev/null
+++ b/drivers/md/dm-vdo/compression.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2026 Rhajune Park <[email protected]>
+ */
+
+#ifndef VDO_COMPRESSION_H
+#define VDO_COMPRESSION_H
+
+#include <linux/types.h>
+
+struct vdo;
+struct vdo_compression_context;
+struct device_config;
+
+#define VDO_DEFAULT_ZSTD_COMPRESSION_LEVEL 1
+
+bool vdo_is_valid_compression_level(int level);
+int vdo_resolve_compression_level(struct device_config *config, char **reason);
+int vdo_make_compression_contexts(struct vdo *vdo);
+void vdo_free_compression_contexts(struct vdo *vdo);
+
+size_t vdo_compress_block(struct vdo *vdo, const void *src, void *dst,
+                         size_t dst_size);
+int vdo_decompress_block(struct vdo *vdo, const void *src, size_t src_size,
+                        void *dst);
+
+#endif /* VDO_COMPRESSION_H */
diff --git a/drivers/md/dm-vdo/data-vio.c b/drivers/md/dm-vdo/data-vio.c
index 370d4239ba31..fb78ef719509 100644
--- a/drivers/md/dm-vdo/data-vio.c
+++ b/drivers/md/dm-vdo/data-vio.c
@@ -13,7 +13,6 @@
 #include <linux/jiffies.h>
 #include <linux/kernel.h>
 #include <linux/list.h>
-#include <linux/lz4.h>
 #include <linux/minmax.h>
 #include <linux/sched.h>
 #include <linux/spinlock.h>
@@ -26,6 +25,7 @@
 #include "permassert.h"
 
 #include "block-map.h"
+#include "compression.h"
 #include "dump.h"
 #include "encodings.h"
 #include "int-map.h"
@@ -1423,7 +1423,6 @@ void release_data_vio_allocation_lock(struct data_vio 
*data_vio, bool reset)
 int uncompress_data_vio(struct data_vio *data_vio,
                        enum block_mapping_state mapping_state, char *buffer)
 {
-       int size;
        u16 fragment_offset, fragment_size;
        struct compressed_block *block = data_vio->compression.block;
        int result = vdo_get_compressed_block_fragment(mapping_state, block,
@@ -1434,10 +1433,10 @@ int uncompress_data_vio(struct data_vio *data_vio,
                return result;
        }
 
-       size = LZ4_decompress_safe((block->data + fragment_offset), buffer,
-                                  fragment_size, VDO_BLOCK_SIZE);
-       if (size != VDO_BLOCK_SIZE) {
-               vdo_log_debug("%s: lz4 error", __func__);
+       result = vdo_decompress_block(vdo_from_data_vio(data_vio),
+                                     block->data + fragment_offset, 
fragment_size, buffer);
+       if (result != VDO_SUCCESS) {
+               vdo_log_debug("%s: decompression error", __func__);
                return VDO_INVALID_FRAGMENT;
        }
 
@@ -1753,7 +1752,7 @@ static void pack_compressed_data(struct vdo_completion 
*completion)
 static void compress_data_vio(struct vdo_completion *completion)
 {
        struct data_vio *data_vio = as_data_vio(completion);
-       int size;
+       size_t size;
 
        assert_data_vio_on_cpu_thread(data_vio);
 
@@ -1761,10 +1760,9 @@ static void compress_data_vio(struct vdo_completion 
*completion)
         * By putting the compressed data at the start of the compressed block 
data field, we won't
         * need to copy it if this data_vio becomes a compressed write agent.
         */
-       size = LZ4_compress_default(data_vio->vio.data,
-                                   data_vio->compression.block->data, 
VDO_BLOCK_SIZE,
-                                   VDO_MAX_COMPRESSED_FRAGMENT_SIZE,
-                                   (char *) vdo_get_work_queue_private_data());
+       size = vdo_compress_block(vdo_from_data_vio(data_vio), 
data_vio->vio.data,
+                                 data_vio->compression.block->data,
+                                 VDO_MAX_COMPRESSED_FRAGMENT_SIZE);
        if ((size > 0) && (size < VDO_COMPRESSED_BLOCK_DATA_SIZE)) {
                data_vio->compression.size = size;
                launch_data_vio_packer_callback(data_vio, pack_compressed_data);
diff --git a/drivers/md/dm-vdo/dm-vdo-target.c 
b/drivers/md/dm-vdo/dm-vdo-target.c
index 1d8375cc3c3e..bbbb60818aac 100644
--- a/drivers/md/dm-vdo/dm-vdo-target.c
+++ b/drivers/md/dm-vdo/dm-vdo-target.c
@@ -18,6 +18,7 @@
 #include "block-map.h"
 #include "completion.h"
 #include "constants.h"
+#include "compression.h"
 #include "data-vio.h"
 #include "dedupe.h"
 #include "dump.h"
@@ -678,6 +679,32 @@ static int parse_one_key_value_pair(const char *key, const 
char *value,
        if (strcmp(key, "compression") == 0)
                return parse_bool(value, "on", "off", &config->compression);
 
+       if (strcmp(key, "compressionAlgorithm") == 0) {
+               if (strcmp(value, "lz4") == 0)
+                       config->compression_algorithm = 
VDO_COMPRESSION_ALGORITHM_LZ4;
+               else if (strcmp(value, "zstd") == 0)
+                       config->compression_algorithm = 
VDO_COMPRESSION_ALGORITHM_ZSTD;
+               else
+                       return -EINVAL;
+
+               return VDO_SUCCESS;
+       }
+
+       if (strcmp(key, "compressionLevel") == 0) {
+               int level;
+
+               result = kstrtoint(value, 10, &level);
+               if (result || !vdo_is_valid_compression_level(level)) {
+                       vdo_log_error("optional parameter error: invalid ZSTD 
compression level \"%s\"",
+                                     value);
+                       return -EINVAL;
+               }
+
+               config->compression_level = level;
+               config->compression_level_set = true;
+               return VDO_SUCCESS;
+       }
+
        if (strcmp(key, "indexSparse") == 0)
                return parse_bool(value, "on", "off", &config->index_sparse);
 
@@ -851,6 +878,8 @@ static int parse_device_config(int argc, char **argv, 
struct dm_target *ti,
        config->max_discard_blocks = 1;
        config->deduplication = true;
        config->compression = false;
+       config->compression_algorithm = VDO_COMPRESSION_ALGORITHM_UNSPECIFIED;
+       config->compression_level = VDO_DEFAULT_ZSTD_COMPRESSION_LEVEL;
        config->index_memory = UDS_MEMORY_CONFIG_256MB;
        config->index_sparse = false;
        config->slab_blocks = DEFAULT_VDO_SLAB_BLOCKS;
@@ -1893,6 +1922,16 @@ static int validate_new_device_config(struct 
device_config *to_validate,
                                      struct device_config *config, bool 
may_grow,
                                      char **error_ptr)
 {
+       if (to_validate->compression_algorithm != 
config->compression_algorithm) {
+               *error_ptr = "Compression algorithm cannot change";
+               return VDO_PARAMETER_MISMATCH;
+       }
+
+       if (to_validate->compression_level != config->compression_level) {
+               *error_ptr = "Compression level cannot change while the VDO is 
active";
+               return VDO_PARAMETER_MISMATCH;
+       }
+
        if (to_validate->owning_target->begin != config->owning_target->begin) {
                *error_ptr = "Starting sector cannot change";
                return VDO_PARAMETER_MISMATCH;
@@ -1943,6 +1982,12 @@ static int prepare_to_modify(struct dm_target *ti, 
struct device_config *config,
        int result;
        bool may_grow = (vdo_get_admin_state(vdo) != 
VDO_ADMIN_STATE_PRE_LOADED);
 
+       if (config->compression_algorithm == 
VDO_COMPRESSION_ALGORITHM_UNSPECIFIED)
+               config->compression_algorithm = 
vdo->device_config->compression_algorithm;
+       result = vdo_resolve_compression_level(config, &ti->error);
+       if (result != VDO_SUCCESS)
+               return -EINVAL;
+
        result = validate_new_device_config(config, vdo->device_config, 
may_grow,
                                            &ti->error);
        if (result != VDO_SUCCESS)
@@ -2983,7 +3028,7 @@ static void vdo_resume(struct dm_target *ti)
 static struct target_type vdo_target_bio = {
        .features = DM_TARGET_SINGLETON,
        .name = "vdo",
-       .version = { 9, 2, 0 },
+       .version = { 9, 3, 0 },
        .module = THIS_MODULE,
        .ctr = vdo_ctr,
        .dtr = vdo_dtr,
diff --git a/drivers/md/dm-vdo/encodings.c b/drivers/md/dm-vdo/encodings.c
index d75e023df637..a388976f3028 100644
--- a/drivers/md/dm-vdo/encodings.c
+++ b/drivers/md/dm-vdo/encodings.c
@@ -22,17 +22,17 @@ struct geometry_block {
        u32 checksum;
 } __packed;
 
-static const struct header GEOMETRY_BLOCK_HEADER_5_0 = {
+static const struct header GEOMETRY_BLOCK_HEADER_5 = {
        .id = VDO_GEOMETRY_BLOCK,
        .version = {
-               .major_version = 5,
-               .minor_version = 0,
+               .major_version = VDO_GEOMETRY_BLOCK_MAJOR_VERSION,
+               .minor_version = VDO_GEOMETRY_BLOCK_MINOR_LZ4,
        },
        /*
         * Note: this size isn't just the payload size following the header, 
like it is everywhere
         * else in VDO.
         */
-       .size = sizeof(struct geometry_block) + sizeof(struct volume_geometry),
+       .size = sizeof(struct geometry_block) + sizeof(struct 
volume_geometry_5_0),
 };
 
 static const struct header GEOMETRY_BLOCK_HEADER_4_0 = {
@@ -291,24 +291,30 @@ static void decode_volume_geometry(u8 *buffer, size_t 
*offset,
  * vdo_encode_volume_geometry() - Encode the on-disk representation of a 
volume geometry into a buffer.
  * @buffer: A buffer to store the encoding.
  * @geometry: The geometry to encode.
- * @version: The geometry block version to encode.
- *
  * Return: VDO_SUCCESS or an error.
  */
-int vdo_encode_volume_geometry(u8 *buffer, const struct volume_geometry 
*geometry,
-                              u32 version)
+int vdo_encode_volume_geometry(u8 *buffer, const struct volume_geometry 
*geometry)
 {
        int result;
        enum volume_region_id id;
        u32 checksum;
        size_t offset = 0;
-       const struct header *header;
+       struct header header = GEOMETRY_BLOCK_HEADER_5;
 
        memcpy(buffer, VDO_GEOMETRY_MAGIC_NUMBER, 
VDO_GEOMETRY_MAGIC_NUMBER_SIZE);
        offset += VDO_GEOMETRY_MAGIC_NUMBER_SIZE;
 
-       header = (version > 4) ? &GEOMETRY_BLOCK_HEADER_5_0 : 
&GEOMETRY_BLOCK_HEADER_4_0;
-       vdo_encode_header(buffer, &offset, header);
+       switch (geometry->compression_algorithm) {
+       case VDO_COMPRESSION_ALGORITHM_LZ4:
+               header.version.minor_version = VDO_GEOMETRY_BLOCK_MINOR_LZ4;
+               break;
+       case VDO_COMPRESSION_ALGORITHM_ZSTD:
+               header.version.minor_version = VDO_GEOMETRY_BLOCK_MINOR_ZSTD;
+               break;
+       default:
+               return VDO_BAD_CONFIGURATION;
+       }
+       vdo_encode_header(buffer, &offset, &header);
 
        /* This is for backwards compatibility */
        encode_u32_le(buffer, &offset, geometry->unused);
@@ -316,8 +322,7 @@ int vdo_encode_volume_geometry(u8 *buffer, const struct 
volume_geometry *geometr
        memcpy(buffer + offset, (unsigned char *) &geometry->uuid, 
sizeof(uuid_t));
        offset += sizeof(uuid_t);
 
-       if (version > 4)
-               encode_u64_le(buffer, &offset, geometry->bio_offset);
+       encode_u64_le(buffer, &offset, geometry->bio_offset);
 
        for (id = 0; id < VDO_VOLUME_REGION_COUNT; id++) {
                encode_u32_le(buffer, &offset, geometry->regions[id].id);
@@ -332,7 +337,7 @@ int vdo_encode_volume_geometry(u8 *buffer, const struct 
volume_geometry *geometr
        else
                buffer[offset++] = 0;
 
-       result = VDO_ASSERT(header->size == offset + sizeof(u32),
+       result = VDO_ASSERT(header.size == offset + sizeof(u32),
                            "should have encoded up to the geometry checksum");
        if (result != VDO_SUCCESS)
                return result;
@@ -352,6 +357,7 @@ int __must_check vdo_parse_geometry_block(u8 *block, struct 
volume_geometry *geo
 {
        u32 checksum, saved_checksum;
        struct header header;
+       struct header expected_header = GEOMETRY_BLOCK_HEADER_5;
        size_t offset = 0;
        int result;
 
@@ -363,9 +369,22 @@ int __must_check vdo_parse_geometry_block(u8 *block, 
struct volume_geometry *geo
        if (header.version.major_version <= 4) {
                result = vdo_validate_header(&GEOMETRY_BLOCK_HEADER_4_0, 
&header,
                                             true, __func__);
+               geometry->compression_algorithm = VDO_COMPRESSION_ALGORITHM_LZ4;
        } else {
-               result = vdo_validate_header(&GEOMETRY_BLOCK_HEADER_5_0, 
&header,
-                                            true, __func__);
+               switch (header.version.minor_version) {
+               case VDO_GEOMETRY_BLOCK_MINOR_LZ4:
+                       geometry->compression_algorithm = 
VDO_COMPRESSION_ALGORITHM_LZ4;
+                       break;
+               case VDO_GEOMETRY_BLOCK_MINOR_ZSTD:
+                       geometry->compression_algorithm = 
VDO_COMPRESSION_ALGORITHM_ZSTD;
+                       break;
+               default:
+                       return validate_version(expected_header.version, 
header.version,
+                                               "geometry block");
+               }
+
+               expected_header.version.minor_version = 
header.version.minor_version;
+               result = vdo_validate_header(&expected_header, &header, true, 
__func__);
        }
        if (result != VDO_SUCCESS)
                return result;
@@ -380,8 +399,10 @@ int __must_check vdo_parse_geometry_block(u8 *block, 
struct volume_geometry *geo
        /* Decode and verify the checksum. */
        checksum = vdo_crc32(block, offset);
        decode_u32_le(block, &offset, &saved_checksum);
+       if (checksum != saved_checksum)
+               return VDO_CHECKSUM_MISMATCH;
 
-       return ((checksum == saved_checksum) ? VDO_SUCCESS : 
VDO_CHECKSUM_MISMATCH);
+       return VDO_SUCCESS;
 }
 
 struct block_map_page *vdo_format_block_map_page(void *buffer, nonce_t nonce,
@@ -1650,12 +1671,14 @@ static int vdo_compute_index_blocks(const struct 
index_config *config,
  * @nonce: The nonce to use to identify the vdo.
  * @uuid: The uuid to use to identify the vdo.
  * @index_config: The config used for structure initialization.
+ * @compression_algorithm: The algorithm to use for compressed data.
  * @geometry: The volume geometry to initialize.
  *
  * Return: VDO_SUCCESS or an error code.
  */
 int vdo_initialize_volume_geometry(nonce_t nonce, uuid_t *uuid,
                                   const struct index_config *index_config,
+                                  enum vdo_compression_algorithm 
compression_algorithm,
                                   struct volume_geometry *geometry)
 {
        int result;
@@ -1669,6 +1692,7 @@ int vdo_initialize_volume_geometry(nonce_t nonce, uuid_t 
*uuid,
                /* This is for backwards compatibility. */
                .unused = 0,
                .nonce = nonce,
+               .compression_algorithm = compression_algorithm,
                .bio_offset = 0,
                .regions = {
                        [VDO_INDEX_REGION] = {
diff --git a/drivers/md/dm-vdo/encodings.h b/drivers/md/dm-vdo/encodings.h
index 67ff0ff2ffda..9c5180a76a48 100644
--- a/drivers/md/dm-vdo/encodings.h
+++ b/drivers/md/dm-vdo/encodings.h
@@ -64,7 +64,12 @@ struct packed_header {
 enum {
        VDO_GEOMETRY_BLOCK_LOCATION = 0,
        VDO_GEOMETRY_MAGIC_NUMBER_SIZE = 8,
-       VDO_DEFAULT_GEOMETRY_BLOCK_VERSION = 5,
+       VDO_GEOMETRY_BLOCK_MAJOR_VERSION = 5,
+};
+
+enum vdo_geometry_block_minor_version {
+       VDO_GEOMETRY_BLOCK_MINOR_LZ4 = VDO_COMPRESSION_ALGORITHM_LZ4,
+       VDO_GEOMETRY_BLOCK_MINOR_ZSTD = VDO_COMPRESSION_ALGORITHM_ZSTD,
 };
 
 struct index_config {
@@ -102,6 +107,18 @@ struct volume_geometry {
        struct volume_region regions[VDO_VOLUME_REGION_COUNT];
        /* The index config */
        struct index_config index_config;
+       /* The algorithm used for compressed data. */
+       enum vdo_compression_algorithm compression_algorithm;
+} __packed;
+
+/* This volume geometry struct is used for sizing version 5. */
+struct volume_geometry_5_0 {
+       u32 unused;
+       nonce_t nonce;
+       uuid_t uuid;
+       block_count_t bio_offset;
+       struct volume_region regions[VDO_VOLUME_REGION_COUNT];
+       struct index_config index_config;
 } __packed;
 
 /* This volume geometry struct is used for sizing only */
@@ -811,10 +828,10 @@ vdo_get_index_region_size(struct volume_geometry geometry)
 
 int vdo_initialize_volume_geometry(nonce_t nonce, uuid_t *uuid,
                                   const struct index_config *index_config,
+                                  enum vdo_compression_algorithm 
compression_algorithm,
                                   struct volume_geometry *geometry);
 
-int vdo_encode_volume_geometry(u8 *buffer, const struct volume_geometry 
*geometry,
-                              u32 version);
+int vdo_encode_volume_geometry(u8 *buffer, const struct volume_geometry 
*geometry);
 int __must_check vdo_parse_geometry_block(unsigned char *block,
                                          struct volume_geometry *geometry);
 
diff --git a/drivers/md/dm-vdo/message-stats.c 
b/drivers/md/dm-vdo/message-stats.c
index b4c919780c22..3d6f035c838e 100644
--- a/drivers/md/dm-vdo/message-stats.c
+++ b/drivers/md/dm-vdo/message-stats.c
@@ -34,6 +34,17 @@ static void write_u32(char *prefix, u32 value, char *suffix, 
char **buf,
        *maxlen -= count;
 }
 
+static void write_s32(char *prefix, s32 value, char *suffix, char **buf,
+                     unsigned int *maxlen)
+{
+       int count;
+
+       count = scnprintf(*buf, *maxlen, "%s%d%s", prefix == NULL ? "" : prefix,
+                         value, suffix == NULL ? "" : suffix);
+       *buf += count;
+       *maxlen -= count;
+}
+
 static void write_block_count_t(char *prefix, block_count_t value, char 
*suffix,
                                char **buf, unsigned int *maxlen)
 {
@@ -461,10 +472,13 @@ static void write_index_config(struct index_config 
*config, char **buf,
 int vdo_write_config(struct vdo *vdo, char **buf, unsigned int *maxlen)
 {
        struct vdo_config *config = &vdo->states.vdo.config;
+       char *compression_algorithm =
+               (vdo->geometry.compression_algorithm == 
VDO_COMPRESSION_ALGORITHM_ZSTD) ?
+               "zstd" : "lz4";
 
        write_string(NULL, "{ ", NULL, buf, maxlen);
        /* version */
-       write_u32("version : ", 1, ", ", buf, maxlen);
+       write_u32("version : ", 3, ", ", buf, maxlen);
        /* physical size */
        write_block_count_t("physicalSize : ", config->physical_blocks * 
VDO_BLOCK_SIZE, ", ",
                            buf, maxlen);
@@ -473,6 +487,10 @@ int vdo_write_config(struct vdo *vdo, char **buf, unsigned 
int *maxlen)
                            buf, maxlen);
        /* slab size */
        write_block_count_t("slabSize : ", config->slab_size, ", ", buf, 
maxlen);
+       write_string("compressionAlgorithm : ", compression_algorithm, ", ", 
buf, maxlen);
+       if (vdo->geometry.compression_algorithm == 
VDO_COMPRESSION_ALGORITHM_ZSTD)
+               write_s32("compressionLevel : ", 
vdo->device_config->compression_level,
+                         ", ", buf, maxlen);
        /* index config */
        write_index_config(&vdo->geometry.index_config, buf, maxlen);
        write_string(NULL, "}", NULL, buf, maxlen);
diff --git a/drivers/md/dm-vdo/types.h b/drivers/md/dm-vdo/types.h
index 0d60a88aa086..8d92a8bef3c3 100644
--- a/drivers/md/dm-vdo/types.h
+++ b/drivers/md/dm-vdo/types.h
@@ -205,6 +205,12 @@ struct thread_count_config {
        unsigned int hash_zones;
 } __packed;
 
+enum vdo_compression_algorithm {
+       VDO_COMPRESSION_ALGORITHM_UNSPECIFIED = -1,
+       VDO_COMPRESSION_ALGORITHM_LZ4 = 0,
+       VDO_COMPRESSION_ALGORITHM_ZSTD = 1,
+};
+
 struct device_config {
        struct dm_target *owning_target;
        struct dm_dev *owned_device;
@@ -225,6 +231,9 @@ struct device_config {
        unsigned int block_map_maximum_age;
        bool deduplication;
        bool compression;
+       enum vdo_compression_algorithm compression_algorithm;
+       int compression_level;
+       bool compression_level_set;
        struct thread_count_config thread_counts;
        block_count_t max_discard_blocks;
        block_count_t slab_blocks;
diff --git a/drivers/md/dm-vdo/vdo.c b/drivers/md/dm-vdo/vdo.c
index d0d4e0262be2..1340279701f6 100644
--- a/drivers/md/dm-vdo/vdo.c
+++ b/drivers/md/dm-vdo/vdo.c
@@ -31,7 +31,6 @@
 
 #include <linux/completion.h>
 #include <linux/device-mapper.h>
-#include <linux/lz4.h>
 #include <linux/mutex.h>
 #include <linux/spinlock.h>
 #include <linux/string.h>
@@ -45,6 +44,7 @@
 
 #include "block-map.h"
 #include "completion.h"
+#include "compression.h"
 #include "data-vio.h"
 #include "dedupe.h"
 #include "encodings.h"
@@ -465,7 +465,8 @@ static int __must_check vdo_format(struct vdo *vdo, char 
**error_ptr)
        };
 
        uuid_gen(&uuid);
-       result = vdo_initialize_volume_geometry(nonce, &uuid, &index_config, 
&vdo->geometry);
+       result = vdo_initialize_volume_geometry(nonce, &uuid, &index_config,
+                                               config->compression_algorithm, 
&vdo->geometry);
        if (result != VDO_SUCCESS) {
                *error_ptr = "Could not initialize volume geometry during 
format";
                return result;
@@ -508,7 +509,6 @@ static int initialize_vdo(struct vdo *vdo, struct 
device_config *config,
                          unsigned int instance, char **reason)
 {
        int result;
-       zone_count_t i;
 
        vdo->device_config = config;
        vdo->starting_sector_offset = config->owning_target->begin;
@@ -540,6 +540,8 @@ static int initialize_vdo(struct vdo *vdo, struct 
device_config *config,
        }
 
        if (mem_is_zero(vdo->geometry_block.vio.data, VDO_BLOCK_SIZE)) {
+               if (config->compression_algorithm == 
VDO_COMPRESSION_ALGORITHM_UNSPECIFIED)
+                       config->compression_algorithm = 
VDO_COMPRESSION_ALGORITHM_LZ4;
                result = vdo_format(vdo, reason);
                if (result != VDO_SUCCESS)
                        return result;
@@ -550,8 +552,19 @@ static int initialize_vdo(struct vdo *vdo, struct 
device_config *config,
                        *reason = "Could not parse geometry block";
                        return result;
                }
+
+               if (config->compression_algorithm != 
VDO_COMPRESSION_ALGORITHM_UNSPECIFIED &&
+                   config->compression_algorithm != 
vdo->geometry.compression_algorithm) {
+                       *reason = "Compression algorithm does not match 
formatted volume";
+                       return VDO_PARAMETER_MISMATCH;
+               }
+               config->compression_algorithm = 
vdo->geometry.compression_algorithm;
        }
 
+       result = vdo_resolve_compression_level(config, reason);
+       if (result != VDO_SUCCESS)
+               return result;
+
        result = initialize_thread_config(config->thread_counts, 
&vdo->thread_config);
        if (result != VDO_SUCCESS) {
                *reason = "Cannot create thread configuration";
@@ -563,23 +576,12 @@ static int initialize_vdo(struct vdo *vdo, struct 
device_config *config,
                     config->thread_counts.physical_zones,
                     config->thread_counts.hash_zones, 
vdo->thread_config.thread_count);
 
-       /* Compression context storage */
-       result = vdo_allocate(config->thread_counts.cpu_threads, "LZ4 context",
-                             &vdo->compression_context);
+       result = vdo_make_compression_contexts(vdo);
        if (result != VDO_SUCCESS) {
-               *reason = "cannot allocate LZ4 context";
+               *reason = "cannot allocate compression contexts";
                return result;
        }
 
-       for (i = 0; i < config->thread_counts.cpu_threads; i++) {
-               result = vdo_allocate(LZ4_MEM_COMPRESS, "LZ4 context",
-                                     &vdo->compression_context[i]);
-               if (result != VDO_SUCCESS) {
-                       *reason = "cannot allocate LZ4 context";
-                       return result;
-               }
-       }
-
        result = register_vdo(vdo);
        if (result != VDO_SUCCESS) {
                *reason = "Cannot add VDO to device registry";
@@ -788,12 +790,7 @@ void vdo_destroy(struct vdo *vdo)
 
        uninitialize_thread_config(&vdo->thread_config);
 
-       if (vdo->compression_context != NULL) {
-               for (i = 0; i < vdo->device_config->thread_counts.cpu_threads; 
i++)
-                       vdo_free(vdo_forget(vdo->compression_context[i]));
-
-               vdo_free(vdo_forget(vdo->compression_context));
-       }
+       vdo_free_compression_contexts(vdo);
        vdo_free(vdo);
 }
 
@@ -1031,8 +1028,7 @@ void vdo_save_geometry_block(struct vdo *vdo, struct 
vdo_completion *parent)
 {
        struct vdo_geometry_block *geometry_block = &vdo->geometry_block;
 
-       vdo_encode_volume_geometry(geometry_block->buffer, &vdo->geometry,
-                                  VDO_DEFAULT_GEOMETRY_BLOCK_VERSION);
+       vdo_encode_volume_geometry(geometry_block->buffer, &vdo->geometry);
        geometry_block->vio.completion.parent = parent;
        geometry_block->vio.completion.callback_thread_id = 
parent->callback_thread_id;
        vdo_submit_metadata_vio(&geometry_block->vio,
diff --git a/drivers/md/dm-vdo/vdo.h b/drivers/md/dm-vdo/vdo.h
index 9a63f5d45ce3..dc09c828b5c6 100644
--- a/drivers/md/dm-vdo/vdo.h
+++ b/drivers/md/dm-vdo/vdo.h
@@ -170,6 +170,8 @@ struct vdo_administrator {
        struct completion callback_sync;
 };
 
+struct vdo_compression_context;
+
 struct vdo {
        char thread_name_prefix[MAX_VDO_WORK_QUEUE_NAME_LEN];
        struct vdo_thread *threads;
@@ -269,8 +271,8 @@ struct vdo {
        u64 starting_sector_offset;
        struct volume_geometry geometry;
 
-       /* N blobs of context data for LZ4 code, one per CPU thread. */
-       char **compression_context;
+       /* Compression state private to each CPU worker. */
+       struct vdo_compression_context **compression_context;
 };
 
 /**
-- 
2.55.0



Reply via email to