| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (C) 2014 Sergey Senozhatsky. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/string.h> |
| #include <linux/err.h> |
| #include <linux/slab.h> |
| #include <linux/wait.h> |
| #include <linux/sched.h> |
| #include <linux/cpu.h> |
| #include <linux/crypto.h> |
| |
| #include "zcomp.h" |
| |
| static const char * const backends[] = { |
| "lzo", |
| "lzo-rle", |
| #if IS_ENABLED(CONFIG_CRYPTO_LZ4) |
| "lz4", |
| #endif |
| #if IS_ENABLED(CONFIG_CRYPTO_LZ4HC) |
| "lz4hc", |
| #endif |
| #if IS_ENABLED(CONFIG_CRYPTO_842) |
| "842", |
| #endif |
| #if IS_ENABLED(CONFIG_CRYPTO_ZSTD) |
| "zstd", |
| #endif |
| NULL |
| }; |
| |
| static void zcomp_strm_free(struct zcomp_strm *zstrm) |
| { |
| if (!IS_ERR_OR_NULL(zstrm->tfm)) |
| crypto_free_comp(zstrm->tfm); |
| free_pages((unsigned long)zstrm->buffer, 1); |
| kfree(zstrm); |
| } |
| |
| /* |
| * allocate new zcomp_strm structure with ->tfm initialized by |
| * backend, return NULL on error |
| */ |
| static struct zcomp_strm *zcomp_strm_alloc(struct zcomp *comp) |
| { |
| struct zcomp_strm *zstrm = kmalloc(sizeof(*zstrm), GFP_KERNEL); |
| if (!zstrm) |
| return NULL; |
| |
| zstrm->tfm = crypto_alloc_comp(comp->name, 0, 0); |
| /* |
| * allocate 2 pages. 1 for compressed data, plus 1 extra for the |
| * case when compressed size is larger than the original one |
| */ |
| zstrm->buffer = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 1); |
| if (IS_ERR_OR_NULL(zstrm->tfm) || !zstrm->buffer) { |
| zcomp_strm_free(zstrm); |
| zstrm = NULL; |
| } |
| return zstrm; |
| } |
| |
| bool zcomp_available_algorithm(const char *comp) |
| { |
| int i; |
| |
| i = __sysfs_match_string(backends, -1, comp); |
| if (i >= 0) |
| return true; |
| |
| /* |
| * Crypto does not ignore a trailing new line symbol, |
| * so make sure you don't supply a string containing |
| * one. |
| * This also means that we permit zcomp initialisation |
| * with any compressing algorithm known to crypto api. |
| */ |
| return crypto_has_comp(comp, 0, 0) == 1; |
| } |
| |
| /* show available compressors */ |
| ssize_t zcomp_available_show(const char *comp, char *buf) |
| { |
| bool known_algorithm = false; |
| ssize_t sz = 0; |
| int i = 0; |
| |
| for (; backends[i]; i++) { |
| if (!strcmp(comp, backends[i])) { |
| known_algorithm = true; |
| sz += scnprintf(buf + sz, PAGE_SIZE - sz - 2, |
| "[%s] ", backends[i]); |
| } else { |
| sz += scnprintf(buf + sz, PAGE_SIZE - sz - 2, |
| "%s ", backends[i]); |
| } |
| } |
| |
| /* |
| * Out-of-tree module known to crypto api or a missing |
| * entry in `backends'. |
| */ |
| if (!known_algorithm && crypto_has_comp(comp, 0, 0) == 1) |
| sz += scnprintf(buf + sz, PAGE_SIZE - sz - 2, |
| "[%s] ", comp); |
| |
| sz += scnprintf(buf + sz, PAGE_SIZE - sz, "\n"); |
| return sz; |
| } |
| |
| struct zcomp_strm *zcomp_stream_get(struct zcomp *comp) |
| { |
| return *get_cpu_ptr(comp->stream); |
| } |
| |
| void zcomp_stream_put(struct zcomp *comp) |
| { |
| put_cpu_ptr(comp->stream); |
| } |
| |
| int zcomp_compress(struct zcomp_strm *zstrm, |
| const void *src, unsigned int *dst_len) |
| { |
| /* |
| * Our dst memory (zstrm->buffer) is always `2 * PAGE_SIZE' sized |
| * because sometimes we can endup having a bigger compressed data |
| * due to various reasons: for example compression algorithms tend |
| * to add some padding to the compressed buffer. Speaking of padding, |
| * comp algorithm `842' pads the compressed length to multiple of 8 |
| * and returns -ENOSP when the dst memory is not big enough, which |
| * is not something that ZRAM wants to see. We can handle the |
| * `compressed_size > PAGE_SIZE' case easily in ZRAM, but when we |
| * receive -ERRNO from the compressing backend we can't help it |
| * anymore. To make `842' happy we need to tell the exact size of |
| * the dst buffer, zram_drv will take care of the fact that |
| * compressed buffer is too big. |
| */ |
| *dst_len = PAGE_SIZE * 2; |
| |
| return crypto_comp_compress(zstrm->tfm, |
| src, PAGE_SIZE, |
| zstrm->buffer, dst_len); |
| } |
| |
| int zcomp_decompress(struct zcomp_strm *zstrm, |
| const void *src, unsigned int src_len, void *dst) |
| { |
| unsigned int dst_len = PAGE_SIZE; |
| |
| return crypto_comp_decompress(zstrm->tfm, |
| src, src_len, |
| dst, &dst_len); |
| } |
| |
| int zcomp_cpu_up_prepare(unsigned int cpu, struct hlist_node *node) |
| { |
| struct zcomp *comp = hlist_entry(node, struct zcomp, node); |
| struct zcomp_strm *zstrm; |
| |
| if (WARN_ON(*per_cpu_ptr(comp->stream, cpu))) |
| return 0; |
| |
| zstrm = zcomp_strm_alloc(comp); |
| if (IS_ERR_OR_NULL(zstrm)) { |
| pr_err("Can't allocate a compression stream\n"); |
| return -ENOMEM; |
| } |
| *per_cpu_ptr(comp->stream, cpu) = zstrm; |
| return 0; |
| } |
| |
| int zcomp_cpu_dead(unsigned int cpu, struct hlist_node *node) |
| { |
| struct zcomp *comp = hlist_entry(node, struct zcomp, node); |
| struct zcomp_strm *zstrm; |
| |
| zstrm = *per_cpu_ptr(comp->stream, cpu); |
| if (!IS_ERR_OR_NULL(zstrm)) |
| zcomp_strm_free(zstrm); |
| *per_cpu_ptr(comp->stream, cpu) = NULL; |
| return 0; |
| } |
| |
| static int zcomp_init(struct zcomp *comp) |
| { |
| int ret; |
| |
| comp->stream = alloc_percpu(struct zcomp_strm *); |
| if (!comp->stream) |
| return -ENOMEM; |
| |
| ret = cpuhp_state_add_instance(CPUHP_ZCOMP_PREPARE, &comp->node); |
| if (ret < 0) |
| goto cleanup; |
| return 0; |
| |
| cleanup: |
| free_percpu(comp->stream); |
| return ret; |
| } |
| |
| void zcomp_destroy(struct zcomp *comp) |
| { |
| cpuhp_state_remove_instance(CPUHP_ZCOMP_PREPARE, &comp->node); |
| free_percpu(comp->stream); |
| kfree(comp); |
| } |
| |
| /* |
| * search available compressors for requested algorithm. |
| * allocate new zcomp and initialize it. return compressing |
| * backend pointer or ERR_PTR if things went bad. ERR_PTR(-EINVAL) |
| * if requested algorithm is not supported, ERR_PTR(-ENOMEM) in |
| * case of allocation error, or any other error potentially |
| * returned by zcomp_init(). |
| */ |
| struct zcomp *zcomp_create(const char *compress) |
| { |
| struct zcomp *comp; |
| int error; |
| |
| if (!zcomp_available_algorithm(compress)) |
| return ERR_PTR(-EINVAL); |
| |
| comp = kzalloc(sizeof(struct zcomp), GFP_KERNEL); |
| if (!comp) |
| return ERR_PTR(-ENOMEM); |
| |
| comp->name = compress; |
| error = zcomp_init(comp); |
| if (error) { |
| kfree(comp); |
| return ERR_PTR(error); |
| } |
| return comp; |
| } |