| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Cryptographic API. | |
| * | |
| * Deflate algorithm (RFC 1951), implemented here primarily for use | |
| * by IPCOMP (RFC 3173 & RFC 2394). | |
| * | |
| * Copyright (c) 2003 James Morris <jmorris@intercode.com.au> | |
| * Copyright (c) 2023 Google, LLC. <ardb@kernel.org> | |
| * Copyright (c) 2025 Herbert Xu <herbert@gondor.apana.org.au> | |
| */ | |
| #include <crypto/internal/acompress.h> | |
| #include <crypto/scatterwalk.h> | |
| #include <linux/init.h> | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/mutex.h> | |
| #include <linux/overflow.h> | |
| #include <linux/percpu.h> | |
| #include <linux/scatterlist.h> | |
| #include <linux/slab.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/zlib.h> | |
| #define DEFLATE_DEF_LEVEL Z_DEFAULT_COMPRESSION | |
| #define DEFLATE_DEF_WINBITS 11 | |
| #define DEFLATE_DEF_MEMLEVEL MAX_MEM_LEVEL | |
| struct deflate_stream { | |
| struct z_stream_s stream; | |
| u8 workspace[]; | |
| }; | |
| static DEFINE_MUTEX(deflate_stream_lock); | |
| static void *deflate_alloc_stream(void) | |
| { | |
| size_t size = max(zlib_inflate_workspacesize(), | |
| zlib_deflate_workspacesize(-DEFLATE_DEF_WINBITS, | |
| DEFLATE_DEF_MEMLEVEL)); | |
| struct deflate_stream *ctx; | |
| ctx = kvmalloc_flex(*ctx, workspace, size); | |
| if (!ctx) | |
| return ERR_PTR(-ENOMEM); | |
| ctx->stream.workspace = ctx->workspace; | |
| return ctx; | |
| } | |
| static void deflate_free_stream(void *ctx) | |
| { | |
| kvfree(ctx); | |
| } | |
| static struct crypto_acomp_streams deflate_streams = { | |
| .alloc_ctx = deflate_alloc_stream, | |
| .free_ctx = deflate_free_stream, | |
| }; | |
| static int deflate_compress_one(struct acomp_req *req, | |
| struct deflate_stream *ds) | |
| { | |
| struct z_stream_s *stream = &ds->stream; | |
| struct acomp_walk walk; | |
| int ret; | |
| ret = acomp_walk_virt(&walk, req, true); | |
| if (ret) | |
| return ret; | |
| do { | |
| unsigned int dcur; | |
| dcur = acomp_walk_next_dst(&walk); | |
| if (!dcur) | |
| return -ENOSPC; | |
| stream->avail_out = dcur; | |
| stream->next_out = walk.dst.virt.addr; | |
| do { | |
| int flush = Z_FINISH; | |
| unsigned int scur; | |
| stream->avail_in = 0; | |
| stream->next_in = NULL; | |
| scur = acomp_walk_next_src(&walk); | |
| if (scur) { | |
| if (acomp_walk_more_src(&walk, scur)) | |
| flush = Z_NO_FLUSH; | |
| stream->avail_in = scur; | |
| stream->next_in = walk.src.virt.addr; | |
| } | |
| ret = zlib_deflate(stream, flush); | |
| if (scur) { | |
| scur -= stream->avail_in; | |
| acomp_walk_done_src(&walk, scur); | |
| } | |
| } while (ret == Z_OK && stream->avail_out); | |
| acomp_walk_done_dst(&walk, dcur); | |
| } while (ret == Z_OK); | |
| if (ret != Z_STREAM_END) | |
| return -EINVAL; | |
| req->dlen = stream->total_out; | |
| return 0; | |
| } | |
| static int deflate_compress(struct acomp_req *req) | |
| { | |
| struct crypto_acomp_stream *s; | |
| struct deflate_stream *ds; | |
| int err; | |
| s = crypto_acomp_lock_stream_bh(&deflate_streams); | |
| ds = s->ctx; | |
| err = zlib_deflateInit2(&ds->stream, DEFLATE_DEF_LEVEL, Z_DEFLATED, | |
| -DEFLATE_DEF_WINBITS, DEFLATE_DEF_MEMLEVEL, | |
| Z_DEFAULT_STRATEGY); | |
| if (err != Z_OK) { | |
| err = -EINVAL; | |
| goto out; | |
| } | |
| err = deflate_compress_one(req, ds); | |
| out: | |
| crypto_acomp_unlock_stream_bh(s); | |
| return err; | |
| } | |
| static int deflate_decompress_one(struct acomp_req *req, | |
| struct deflate_stream *ds) | |
| { | |
| struct z_stream_s *stream = &ds->stream; | |
| bool out_of_space = false; | |
| struct acomp_walk walk; | |
| int ret; | |
| ret = acomp_walk_virt(&walk, req, true); | |
| if (ret) | |
| return ret; | |
| do { | |
| unsigned int scur; | |
| stream->avail_in = 0; | |
| stream->next_in = NULL; | |
| scur = acomp_walk_next_src(&walk); | |
| if (scur) { | |
| stream->avail_in = scur; | |
| stream->next_in = walk.src.virt.addr; | |
| } | |
| do { | |
| unsigned int dcur; | |
| unsigned long avail_in; | |
| dcur = acomp_walk_next_dst(&walk); | |
| stream->avail_out = dcur; | |
| stream->next_out = walk.dst.virt.addr; | |
| avail_in = stream->avail_in; | |
| ret = zlib_inflate(stream, Z_NO_FLUSH); | |
| if (!dcur && avail_in == stream->avail_in) { | |
| out_of_space = true; | |
| break; | |
| } | |
| dcur -= stream->avail_out; | |
| acomp_walk_done_dst(&walk, dcur); | |
| } while (ret == Z_OK && stream->avail_in); | |
| if (scur) | |
| acomp_walk_done_src(&walk, scur); | |
| if (out_of_space) | |
| return -ENOSPC; | |
| } while (ret == Z_OK); | |
| if (ret != Z_STREAM_END) | |
| return -EINVAL; | |
| req->dlen = stream->total_out; | |
| return 0; | |
| } | |
| static int deflate_decompress(struct acomp_req *req) | |
| { | |
| struct crypto_acomp_stream *s; | |
| struct deflate_stream *ds; | |
| int err; | |
| s = crypto_acomp_lock_stream_bh(&deflate_streams); | |
| ds = s->ctx; | |
| err = zlib_inflateInit2(&ds->stream, -DEFLATE_DEF_WINBITS); | |
| if (err != Z_OK) { | |
| err = -EINVAL; | |
| goto out; | |
| } | |
| err = deflate_decompress_one(req, ds); | |
| out: | |
| crypto_acomp_unlock_stream_bh(s); | |
| return err; | |
| } | |
| static int deflate_init(struct crypto_acomp *tfm) | |
| { | |
| int ret; | |
| mutex_lock(&deflate_stream_lock); | |
| ret = crypto_acomp_alloc_streams(&deflate_streams); | |
| mutex_unlock(&deflate_stream_lock); | |
| return ret; | |
| } | |
| static struct acomp_alg acomp = { | |
| .compress = deflate_compress, | |
| .decompress = deflate_decompress, | |
| .init = deflate_init, | |
| .base.cra_name = "deflate", | |
| .base.cra_driver_name = "deflate-generic", | |
| .base.cra_flags = CRYPTO_ALG_REQ_VIRT, | |
| .base.cra_module = THIS_MODULE, | |
| }; | |
| static int __init deflate_mod_init(void) | |
| { | |
| return crypto_register_acomp(&acomp); | |
| } | |
| static void __exit deflate_mod_fini(void) | |
| { | |
| crypto_unregister_acomp(&acomp); | |
| crypto_acomp_free_streams(&deflate_streams); | |
| } | |
| module_init(deflate_mod_init); | |
| module_exit(deflate_mod_fini); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_DESCRIPTION("Deflate Compression Algorithm for IPCOMP"); | |
| MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>"); | |
| MODULE_AUTHOR("Ard Biesheuvel <ardb@kernel.org>"); | |
| MODULE_AUTHOR("Herbert Xu <herbert@gondor.apana.org.au>"); | |
| MODULE_ALIAS_CRYPTO("deflate"); | |
| MODULE_ALIAS_CRYPTO("deflate-generic"); |