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
