| // SPDX-License-Identifier: GPL-2.0-or-later | |
| #include <linux/kernel.h> | |
| #include <linux/slab.h> | |
| #include <linux/vmalloc.h> | |
| #include <linux/zstd.h> | |
| #include "backend_zstd.h" | |
| struct zstd_ctx { | |
| zstd_cctx *cctx; | |
| zstd_dctx *dctx; | |
| void *cctx_mem; | |
| void *dctx_mem; | |
| }; | |
| struct zstd_params { | |
| zstd_custom_mem custom_mem; | |
| zstd_cdict *cdict; | |
| zstd_ddict *ddict; | |
| zstd_parameters cprm; | |
| }; | |
| /* | |
| * For C/D dictionaries we need to provide zstd with zstd_custom_mem, | |
| * which zstd uses internally to allocate/free memory when needed. | |
| * | |
| * This means that allocator.customAlloc() can be called from zcomp_compress() | |
| * under local-lock (per-CPU compression stream), in which case we must use | |
| * GFP_ATOMIC. | |
| * | |
| * Another complication here is that we can be configured as a swap device. | |
| */ | |
| static void *zstd_custom_alloc(void *opaque, size_t size) | |
| { | |
| if (!preemptible()) | |
| return kvzalloc(size, GFP_ATOMIC); | |
| return kvzalloc(size, __GFP_KSWAPD_RECLAIM | __GFP_NOWARN); | |
| } | |
| static void zstd_custom_free(void *opaque, void *address) | |
| { | |
| kvfree(address); | |
| } | |
| static void zstd_release_params(struct zcomp_params *params) | |
| { | |
| struct zstd_params *zp = params->drv_data; | |
| params->drv_data = NULL; | |
| if (!zp) | |
| return; | |
| zstd_free_cdict(zp->cdict); | |
| zstd_free_ddict(zp->ddict); | |
| kfree(zp); | |
| } | |
| static int zstd_setup_params(struct zcomp_params *params) | |
| { | |
| zstd_compression_parameters prm; | |
| struct zstd_params *zp; | |
| zp = kzalloc(sizeof(*zp), GFP_KERNEL); | |
| if (!zp) | |
| return -ENOMEM; | |
| params->drv_data = zp; | |
| if (params->level == ZCOMP_PARAM_NO_LEVEL) | |
| params->level = zstd_default_clevel(); | |
| zp->cprm = zstd_get_params(params->level, PAGE_SIZE); | |
| zp->custom_mem.customAlloc = zstd_custom_alloc; | |
| zp->custom_mem.customFree = zstd_custom_free; | |
| prm = zstd_get_cparams(params->level, PAGE_SIZE, | |
| params->dict_sz); | |
| zp->cdict = zstd_create_cdict_byreference(params->dict, | |
| params->dict_sz, | |
| prm, | |
| zp->custom_mem); | |
| if (!zp->cdict) | |
| goto error; | |
| zp->ddict = zstd_create_ddict_byreference(params->dict, | |
| params->dict_sz, | |
| zp->custom_mem); | |
| if (!zp->ddict) | |
| goto error; | |
| return 0; | |
| error: | |
| zstd_release_params(params); | |
| return -EINVAL; | |
| } | |
| static void zstd_destroy(struct zcomp_ctx *ctx) | |
| { | |
| struct zstd_ctx *zctx = ctx->context; | |
| if (!zctx) | |
| return; | |
| /* | |
| * If ->cctx_mem and ->dctx_mem were allocated then we didn't use | |
| * C/D dictionary and ->cctx / ->dctx were "embedded" into these | |
| * buffers. | |
| * | |
| * If otherwise then we need to explicitly release ->cctx / ->dctx. | |
| */ | |
| if (zctx->cctx_mem) | |
| vfree(zctx->cctx_mem); | |
| else | |
| zstd_free_cctx(zctx->cctx); | |
| if (zctx->dctx_mem) | |
| vfree(zctx->dctx_mem); | |
| else | |
| zstd_free_dctx(zctx->dctx); | |
| kfree(zctx); | |
| } | |
| static int zstd_create(struct zcomp_params *params, struct zcomp_ctx *ctx) | |
| { | |
| struct zstd_ctx *zctx; | |
| zstd_parameters prm; | |
| size_t sz; | |
| zctx = kzalloc(sizeof(*zctx), GFP_KERNEL); | |
| if (!zctx) | |
| return -ENOMEM; | |
| ctx->context = zctx; | |
| if (params->dict_sz == 0) { | |
| prm = zstd_get_params(params->level, PAGE_SIZE); | |
| sz = zstd_cctx_workspace_bound(&prm.cParams); | |
| zctx->cctx_mem = vzalloc(sz); | |
| if (!zctx->cctx_mem) | |
| goto error; | |
| zctx->cctx = zstd_init_cctx(zctx->cctx_mem, sz); | |
| if (!zctx->cctx) | |
| goto error; | |
| sz = zstd_dctx_workspace_bound(); | |
| zctx->dctx_mem = vzalloc(sz); | |
| if (!zctx->dctx_mem) | |
| goto error; | |
| zctx->dctx = zstd_init_dctx(zctx->dctx_mem, sz); | |
| if (!zctx->dctx) | |
| goto error; | |
| } else { | |
| struct zstd_params *zp = params->drv_data; | |
| zctx->cctx = zstd_create_cctx_advanced(zp->custom_mem); | |
| if (!zctx->cctx) | |
| goto error; | |
| zctx->dctx = zstd_create_dctx_advanced(zp->custom_mem); | |
| if (!zctx->dctx) | |
| goto error; | |
| } | |
| return 0; | |
| error: | |
| zstd_release_params(params); | |
| zstd_destroy(ctx); | |
| return -EINVAL; | |
| } | |
| static int zstd_compress(struct zcomp_params *params, struct zcomp_ctx *ctx, | |
| struct zcomp_req *req) | |
| { | |
| struct zstd_params *zp = params->drv_data; | |
| struct zstd_ctx *zctx = ctx->context; | |
| size_t ret; | |
| if (params->dict_sz == 0) | |
| ret = zstd_compress_cctx(zctx->cctx, req->dst, req->dst_len, | |
| req->src, req->src_len, &zp->cprm); | |
| else | |
| ret = zstd_compress_using_cdict(zctx->cctx, req->dst, | |
| req->dst_len, req->src, | |
| req->src_len, | |
| zp->cdict); | |
| if (zstd_is_error(ret)) | |
| return -EINVAL; | |
| req->dst_len = ret; | |
| return 0; | |
| } | |
| static int zstd_decompress(struct zcomp_params *params, struct zcomp_ctx *ctx, | |
| struct zcomp_req *req) | |
| { | |
| struct zstd_params *zp = params->drv_data; | |
| struct zstd_ctx *zctx = ctx->context; | |
| size_t ret; | |
| if (params->dict_sz == 0) | |
| ret = zstd_decompress_dctx(zctx->dctx, req->dst, req->dst_len, | |
| req->src, req->src_len); | |
| else | |
| ret = zstd_decompress_using_ddict(zctx->dctx, req->dst, | |
| req->dst_len, req->src, | |
| req->src_len, zp->ddict); | |
| if (zstd_is_error(ret)) | |
| return -EINVAL; | |
| return 0; | |
| } | |
| const struct zcomp_ops backend_zstd = { | |
| .compress = zstd_compress, | |
| .decompress = zstd_decompress, | |
| .create_ctx = zstd_create, | |
| .destroy_ctx = zstd_destroy, | |
| .setup_params = zstd_setup_params, | |
| .release_params = zstd_release_params, | |
| .name = "zstd", | |
| }; |