Message ID | 20230103184257.118069-2-dima@arista.com |
---|---|
State | New |
Headers | show |
Series | [v2,1/5] crypto: Introduce crypto_pool | expand |
On Tue, 3 Jan 2023 18:42:53 +0000 Dmitry Safonov wrote: > Introduce a per-CPU pool of async crypto requests that can be used > in bh-disabled contexts (designed with net RX/TX softirqs as users in > mind). Allocation can sleep and is a slow-path. > Initial implementation has only ahash as a backend and a fix-sized array > of possible algorithms used in parallel. > > Signed-off-by: Dmitry Safonov <dima@arista.com> > +config CRYPTO_POOL > + tristate "Per-CPU crypto pool" > + default n > + help > + Per-CPU pool of crypto requests ready for usage in atomic contexts. Let's make it a hidden symbol? It seems like a low-level library which gets select'ed, so no point bothering users with questions. config CRYPTO_POOL tristate that's it. > +static int crypto_pool_scratch_alloc(void) This isn't called by anything in this patch.. crypto_pool_alloc_ahash() should call it I'm guessing? > +{ > + int cpu; > + > + lockdep_assert_held(&cpool_mutex); > + > + for_each_possible_cpu(cpu) { > + void *scratch = per_cpu(crypto_pool_scratch, cpu); > + > + if (scratch) > + continue; > + > + scratch = kmalloc_node(scratch_size, GFP_KERNEL, > + cpu_to_node(cpu)); > + if (!scratch) > + return -ENOMEM; > + per_cpu(crypto_pool_scratch, cpu) = scratch; > + } > + return 0; > +} > +out_free: > + if (!IS_ERR_OR_NULL(hash) && e->needs_key) > + crypto_free_ahash(hash); > + > + for_each_possible_cpu(cpu) { > + if (*per_cpu_ptr(e->req, cpu) == NULL) > + break; > + hash = crypto_ahash_reqtfm(*per_cpu_ptr(e->req, cpu)); Could you use a local variable here instead of @hash? That way you won't need the two separate free_ahash() one before and one after the loop.. > + ahash_request_free(*per_cpu_ptr(e->req, cpu)); I think using @req here would be beneficial as well :S > + if (e->needs_key) { > + crypto_free_ahash(hash); > + hash = NULL; > + } > + } > + > + if (hash) > + crypto_free_ahash(hash); This error handling is tricky as hell, please just add a separate variable to hold the > +out_free_req: > + free_percpu(e->req); > +out_free_alg: > + kfree(e->alg); > + e->alg = NULL; > + return ret; > +} > + > +/** > + * crypto_pool_alloc_ahash - allocates pool for ahash requests > + * @alg: name of async hash algorithm > + */ > +int crypto_pool_alloc_ahash(const char *alg) > +{ > + int i, ret; > + > + /* slow-path */ > + mutex_lock(&cpool_mutex); > + > + for (i = 0; i < cpool_populated; i++) { > + if (cpool[i].alg && !strcmp(cpool[i].alg, alg)) { > + if (kref_read(&cpool[i].kref) > 0) { In the current design we can as well resurrect a pool waiting to be destroyed, right? Just reinit the ref and we're good. Otherwise the read() + get() looks quite suspicious to a reader. > + kref_get(&cpool[i].kref); > + ret = i; > + goto out; > + } else { > + break; > + } > + } > + } > + > + for (i = 0; i < cpool_populated; i++) { > + if (!cpool[i].alg) > + break; > + } > + if (i >= CPOOL_SIZE) { > + ret = -ENOSPC; > + goto out; > + } > + > + ret = __cpool_alloc_ahash(&cpool[i], alg); > + if (!ret) { > + ret = i; > + if (i == cpool_populated) > + cpool_populated++; > + } > +out: > + mutex_unlock(&cpool_mutex); > + return ret; > +} > +EXPORT_SYMBOL_GPL(crypto_pool_alloc_ahash); > +/** > + * crypto_pool_add - increases number of users (refcounter) for a pool > + * @id: crypto_pool that was previously allocated by crypto_pool_alloc_ahash() > + */ > +void crypto_pool_add(unsigned int id) > +{ > + if (WARN_ON_ONCE(id > cpool_populated || !cpool[id].alg)) > + return; > + kref_get(&cpool[id].kref); > +} > +EXPORT_SYMBOL_GPL(crypto_pool_add); > + > +/** > + * crypto_pool_get - disable bh and start using crypto_pool > + * @id: crypto_pool that was previously allocated by crypto_pool_alloc_ahash() > + * @c: returned crypto_pool for usage (uninitialized on failure) > + */ > +int crypto_pool_get(unsigned int id, struct crypto_pool *c) Is there a precedent somewhere for the _add() and _get() semantics you're using here? I don't think I've seen _add() for taking a reference, maybe _get() -> start(), _add() -> _get()? > +{ > + struct crypto_pool_ahash *ret = (struct crypto_pool_ahash *)c; > + > + local_bh_disable(); > + if (WARN_ON_ONCE(id > cpool_populated || !cpool[id].alg)) { > + local_bh_enable(); > + return -EINVAL; > + } > + ret->req = *this_cpu_ptr(cpool[id].req); > + ret->base.scratch = this_cpu_read(crypto_pool_scratch); > + return 0; > +} > +EXPORT_SYMBOL_GPL(crypto_pool_get);
Hi Jakub, Thanks for taking a look and your review, On 1/7/23 01:53, Jakub Kicinski wrote: [..] >> +config CRYPTO_POOL >> + tristate "Per-CPU crypto pool" >> + default n >> + help >> + Per-CPU pool of crypto requests ready for usage in atomic contexts. > > Let's make it a hidden symbol? It seems like a low-level library > which gets select'ed, so no point bothering users with questions. > > config CRYPTO_POOL > tristate > > that's it. Sounds good >> +static int crypto_pool_scratch_alloc(void) > > This isn't called by anything in this patch.. > crypto_pool_alloc_ahash() should call it I'm guessing? Ah, this is little historical left-over: in the beginning, I used constant-sized area as "scratch" buffer, the way TCP-MD5 does it. Later, while converting users to crypto_pool, I found that it would be helpful to support simple resizing as users have different size requirement to the temporary buffer, i.e. looking at xfrm_ipcomp, if later it would be converted to use the same API, rather than its own: IPCOMP_SCRATCH_SIZE is huge (which may help to save quite some memory if shared with other crypto_pool users: as the buffer is as well protected by bh-disabled section, the usage pattern is quite the same). In patch 2 I rewrote it for crypto_pool_reserve_scratch(). The purpose of patch 2 was to only add dynamic up-sizing of this buffer to make it easier to review the change. So, here are 2 options: - I can move scratch area allocation/resizing/freeing to patch2 for v3 - Or I can keep patch 2 for only adding the resizing functionality, but in patch 1 make crypto_pool_scratch_alloc() non-static and to the header API. What would you prefer? [..] >> +out_free: >> + if (!IS_ERR_OR_NULL(hash) && e->needs_key) >> + crypto_free_ahash(hash); >> + >> + for_each_possible_cpu(cpu) { >> + if (*per_cpu_ptr(e->req, cpu) == NULL) >> + break; >> + hash = crypto_ahash_reqtfm(*per_cpu_ptr(e->req, cpu)); > > Could you use a local variable here instead of @hash? > That way you won't need the two separate free_ahash() > one before and one after the loop.. Good idea, will do > >> + ahash_request_free(*per_cpu_ptr(e->req, cpu)); > > I think using @req here would be beneficial as well :S > >> + if (e->needs_key) { >> + crypto_free_ahash(hash); >> + hash = NULL; >> + } >> + } >> + >> + if (hash) >> + crypto_free_ahash(hash); > > This error handling is tricky as hell, please just add a separate > variable to hold the Agree, will do for v3 >> +out_free_req: >> + free_percpu(e->req); >> +out_free_alg: >> + kfree(e->alg); >> + e->alg = NULL; >> + return ret; >> +} >> + >> +/** >> + * crypto_pool_alloc_ahash - allocates pool for ahash requests >> + * @alg: name of async hash algorithm >> + */ >> +int crypto_pool_alloc_ahash(const char *alg) >> +{ >> + int i, ret; >> + >> + /* slow-path */ >> + mutex_lock(&cpool_mutex); >> + >> + for (i = 0; i < cpool_populated; i++) { >> + if (cpool[i].alg && !strcmp(cpool[i].alg, alg)) { >> + if (kref_read(&cpool[i].kref) > 0) { > > In the current design we can as well resurrect a pool waiting to > be destroyed, right? Just reinit the ref and we're good. > > Otherwise the read() + get() looks quite suspicious to a reader. Yes, unsure why I haven't done it from the beginning [..] >> +/** >> + * crypto_pool_add - increases number of users (refcounter) for a pool >> + * @id: crypto_pool that was previously allocated by crypto_pool_alloc_ahash() >> + */ >> +void crypto_pool_add(unsigned int id) >> +{ >> + if (WARN_ON_ONCE(id > cpool_populated || !cpool[id].alg)) >> + return; >> + kref_get(&cpool[id].kref); >> +} >> +EXPORT_SYMBOL_GPL(crypto_pool_add); >> + >> +/** >> + * crypto_pool_get - disable bh and start using crypto_pool >> + * @id: crypto_pool that was previously allocated by crypto_pool_alloc_ahash() >> + * @c: returned crypto_pool for usage (uninitialized on failure) >> + */ >> +int crypto_pool_get(unsigned int id, struct crypto_pool *c) > > Is there a precedent somewhere for the _add() and _get() semantics > you're using here? I don't think I've seen _add() for taking a > reference, maybe _get() -> start(), _add() -> _get()? Yeah, I presume I took not the best-fitting naming from tcp_get_md5sig_pool()/tcp_put_md5sig_pool(). Will do the renaming. Thanks, Dmitry
On 1/9/23 20:59, Dmitry Safonov wrote: > Hi Jakub, > > Thanks for taking a look and your review, > > On 1/7/23 01:53, Jakub Kicinski wrote: [..] >>> +static int crypto_pool_scratch_alloc(void) >> >> This isn't called by anything in this patch.. >> crypto_pool_alloc_ahash() should call it I'm guessing? > > Ah, this is little historical left-over: in the beginning, I used > constant-sized area as "scratch" buffer, the way TCP-MD5 does it. > Later, while converting users to crypto_pool, I found that it would be > helpful to support simple resizing as users have different size > requirement to the temporary buffer, i.e. looking at xfrm_ipcomp, if > later it would be converted to use the same API, rather than its own: > IPCOMP_SCRATCH_SIZE is huge (which may help to save quite some memory if > shared with other crypto_pool users: as the buffer is as well protected > by bh-disabled section, the usage pattern is quite the same). > > In patch 2 I rewrote it for crypto_pool_reserve_scratch(). The purpose > of patch 2 was to only add dynamic up-sizing of this buffer to make it > easier to review the change. So, here are 2 options: > - I can move scratch area allocation/resizing/freeing to patch2 for v3 > - Or I can keep patch 2 for only adding the resizing functionality, but > in patch 1 make crypto_pool_scratch_alloc() non-static and to the header > API. > > What would you prefer? Taking the question off: in v3 I'll provide "size" as another argument in patch 2 (the way you suggested it in review for patch 2). That way dynamic allocation would be still separated in patch 2. Thanks, Dmitry
diff --git a/crypto/Kconfig b/crypto/Kconfig index 9c86f7045157..ba8d4a1f10f9 100644 --- a/crypto/Kconfig +++ b/crypto/Kconfig @@ -1388,6 +1388,12 @@ endmenu config CRYPTO_HASH_INFO bool +config CRYPTO_POOL + tristate "Per-CPU crypto pool" + default n + help + Per-CPU pool of crypto requests ready for usage in atomic contexts. + if !KMSAN # avoid false positives from assembly if ARM source "arch/arm/crypto/Kconfig" diff --git a/crypto/Makefile b/crypto/Makefile index d0126c915834..eed8f61bc93b 100644 --- a/crypto/Makefile +++ b/crypto/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_CRYPTO_ACOMP2) += crypto_acompress.o cryptomgr-y := algboss.o testmgr.o obj-$(CONFIG_CRYPTO_MANAGER2) += cryptomgr.o +obj-$(CONFIG_CRYPTO_POOL) += crypto_pool.o obj-$(CONFIG_CRYPTO_USER) += crypto_user.o crypto_user-y := crypto_user_base.o crypto_user-$(CONFIG_CRYPTO_STATS) += crypto_user_stat.o diff --git a/crypto/crypto_pool.c b/crypto/crypto_pool.c new file mode 100644 index 000000000000..37131952c5a7 --- /dev/null +++ b/crypto/crypto_pool.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <crypto/pool.h> +#include <linux/kref.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/percpu.h> +#include <linux/workqueue.h> + +static unsigned long scratch_size = DEFAULT_CRYPTO_POOL_SCRATCH_SZ; +static DEFINE_PER_CPU(void *, crypto_pool_scratch); + +struct crypto_pool_entry { + struct ahash_request * __percpu *req; + const char *alg; + struct kref kref; + bool needs_key; +}; + +#define CPOOL_SIZE (PAGE_SIZE/sizeof(struct crypto_pool_entry)) +static struct crypto_pool_entry cpool[CPOOL_SIZE]; +static unsigned int cpool_populated; +static DEFINE_MUTEX(cpool_mutex); + +static int crypto_pool_scratch_alloc(void) +{ + int cpu; + + lockdep_assert_held(&cpool_mutex); + + for_each_possible_cpu(cpu) { + void *scratch = per_cpu(crypto_pool_scratch, cpu); + + if (scratch) + continue; + + scratch = kmalloc_node(scratch_size, GFP_KERNEL, + cpu_to_node(cpu)); + if (!scratch) + return -ENOMEM; + per_cpu(crypto_pool_scratch, cpu) = scratch; + } + return 0; +} + +static void crypto_pool_scratch_free(void) +{ + int cpu; + + lockdep_assert_held(&cpool_mutex); + + for_each_possible_cpu(cpu) { + void *scratch = per_cpu(crypto_pool_scratch, cpu); + + if (!scratch) + continue; + per_cpu(crypto_pool_scratch, cpu) = NULL; + kfree(scratch); + } +} + +static int __cpool_alloc_ahash(struct crypto_pool_entry *e, const char *alg) +{ + struct crypto_ahash *hash; + int cpu, ret = -ENOMEM; + + e->alg = kstrdup(alg, GFP_KERNEL); + if (!e->alg) + return -ENOMEM; + + e->req = alloc_percpu(struct ahash_request *); + if (!e->req) + goto out_free_alg; + + hash = crypto_alloc_ahash(alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(hash)) { + ret = PTR_ERR(hash); + goto out_free_req; + } + + /* If hash has .setkey(), allocate ahash per-cpu, not only request */ + e->needs_key = crypto_ahash_get_flags(hash) & CRYPTO_TFM_NEED_KEY; + + for_each_possible_cpu(cpu) { + struct ahash_request *req; + + if (!hash) + hash = crypto_alloc_ahash(alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(hash)) + goto out_free; + + req = ahash_request_alloc(hash, GFP_KERNEL); + if (!req) + goto out_free; + + ahash_request_set_callback(req, 0, NULL, NULL); + + *per_cpu_ptr(e->req, cpu) = req; + + if (e->needs_key) + hash = NULL; + } + kref_init(&e->kref); + return 0; + +out_free: + if (!IS_ERR_OR_NULL(hash) && e->needs_key) + crypto_free_ahash(hash); + + for_each_possible_cpu(cpu) { + if (*per_cpu_ptr(e->req, cpu) == NULL) + break; + hash = crypto_ahash_reqtfm(*per_cpu_ptr(e->req, cpu)); + ahash_request_free(*per_cpu_ptr(e->req, cpu)); + if (e->needs_key) { + crypto_free_ahash(hash); + hash = NULL; + } + } + + if (hash) + crypto_free_ahash(hash); +out_free_req: + free_percpu(e->req); +out_free_alg: + kfree(e->alg); + e->alg = NULL; + return ret; +} + +/** + * crypto_pool_alloc_ahash - allocates pool for ahash requests + * @alg: name of async hash algorithm + */ +int crypto_pool_alloc_ahash(const char *alg) +{ + int i, ret; + + /* slow-path */ + mutex_lock(&cpool_mutex); + + for (i = 0; i < cpool_populated; i++) { + if (cpool[i].alg && !strcmp(cpool[i].alg, alg)) { + if (kref_read(&cpool[i].kref) > 0) { + kref_get(&cpool[i].kref); + ret = i; + goto out; + } else { + break; + } + } + } + + for (i = 0; i < cpool_populated; i++) { + if (!cpool[i].alg) + break; + } + if (i >= CPOOL_SIZE) { + ret = -ENOSPC; + goto out; + } + + ret = __cpool_alloc_ahash(&cpool[i], alg); + if (!ret) { + ret = i; + if (i == cpool_populated) + cpool_populated++; + } +out: + mutex_unlock(&cpool_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(crypto_pool_alloc_ahash); + +static void __cpool_free_entry(struct crypto_pool_entry *e) +{ + struct crypto_ahash *hash = NULL; + int cpu; + + for_each_possible_cpu(cpu) { + if (*per_cpu_ptr(e->req, cpu) == NULL) + continue; + + hash = crypto_ahash_reqtfm(*per_cpu_ptr(e->req, cpu)); + ahash_request_free(*per_cpu_ptr(e->req, cpu)); + if (e->needs_key) { + crypto_free_ahash(hash); + hash = NULL; + } + } + if (hash) + crypto_free_ahash(hash); + free_percpu(e->req); + kfree(e->alg); + memset(e, 0, sizeof(*e)); +} + +static void cpool_cleanup_work_cb(struct work_struct *work) +{ + unsigned int i; + bool free_scratch = true; + + mutex_lock(&cpool_mutex); + for (i = 0; i < cpool_populated; i++) { + if (kref_read(&cpool[i].kref) > 0) { + free_scratch = false; + continue; + } + if (!cpool[i].alg) + continue; + __cpool_free_entry(&cpool[i]); + } + if (free_scratch) + crypto_pool_scratch_free(); + mutex_unlock(&cpool_mutex); +} + +static DECLARE_WORK(cpool_cleanup_work, cpool_cleanup_work_cb); +static void cpool_schedule_cleanup(struct kref *kref) +{ + schedule_work(&cpool_cleanup_work); +} + +/** + * crypto_pool_release - decreases number of users for a pool. If it was + * the last user of the pool, releases any memory that was consumed. + * @id: crypto_pool that was previously allocated by crypto_pool_alloc_ahash() + */ +void crypto_pool_release(unsigned int id) +{ + if (WARN_ON_ONCE(id > cpool_populated || !cpool[id].alg)) + return; + + /* slow-path */ + kref_put(&cpool[id].kref, cpool_schedule_cleanup); +} +EXPORT_SYMBOL_GPL(crypto_pool_release); + +/** + * crypto_pool_add - increases number of users (refcounter) for a pool + * @id: crypto_pool that was previously allocated by crypto_pool_alloc_ahash() + */ +void crypto_pool_add(unsigned int id) +{ + if (WARN_ON_ONCE(id > cpool_populated || !cpool[id].alg)) + return; + kref_get(&cpool[id].kref); +} +EXPORT_SYMBOL_GPL(crypto_pool_add); + +/** + * crypto_pool_get - disable bh and start using crypto_pool + * @id: crypto_pool that was previously allocated by crypto_pool_alloc_ahash() + * @c: returned crypto_pool for usage (uninitialized on failure) + */ +int crypto_pool_get(unsigned int id, struct crypto_pool *c) +{ + struct crypto_pool_ahash *ret = (struct crypto_pool_ahash *)c; + + local_bh_disable(); + if (WARN_ON_ONCE(id > cpool_populated || !cpool[id].alg)) { + local_bh_enable(); + return -EINVAL; + } + ret->req = *this_cpu_ptr(cpool[id].req); + ret->base.scratch = this_cpu_read(crypto_pool_scratch); + return 0; +} +EXPORT_SYMBOL_GPL(crypto_pool_get); + +/** + * crypto_pool_algo - return algorithm of crypto_pool + * @id: crypto_pool that was previously allocated by crypto_pool_alloc_ahash() + * @buf: buffer to return name of algorithm + * @buf_len: size of @buf + */ +size_t crypto_pool_algo(unsigned int id, char *buf, size_t buf_len) +{ + size_t ret = 0; + + /* slow-path */ + mutex_lock(&cpool_mutex); + if (cpool[id].alg) + ret = strscpy(buf, cpool[id].alg, buf_len); + mutex_unlock(&cpool_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(crypto_pool_algo); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Per-CPU pool of crypto requests"); diff --git a/include/crypto/pool.h b/include/crypto/pool.h new file mode 100644 index 000000000000..2c61aa45faff --- /dev/null +++ b/include/crypto/pool.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _CRYPTO_POOL_H +#define _CRYPTO_POOL_H + +#include <crypto/hash.h> + +#define DEFAULT_CRYPTO_POOL_SCRATCH_SZ 128 + +struct crypto_pool { + void *scratch; +}; + +/* + * struct crypto_pool_ahash - per-CPU pool of ahash_requests + * @base: common members that can be used by any async crypto ops + * @req: pre-allocated ahash request + */ +struct crypto_pool_ahash { + struct crypto_pool base; + struct ahash_request *req; +}; + +int crypto_pool_alloc_ahash(const char *alg); +void crypto_pool_add(unsigned int id); +void crypto_pool_release(unsigned int id); + +int crypto_pool_get(unsigned int id, struct crypto_pool *c); +static inline void crypto_pool_put(void) +{ + local_bh_enable(); +} +size_t crypto_pool_algo(unsigned int id, char *buf, size_t buf_len); + +#endif /* _CRYPTO_POOL_H */
Introduce a per-CPU pool of async crypto requests that can be used in bh-disabled contexts (designed with net RX/TX softirqs as users in mind). Allocation can sleep and is a slow-path. Initial implementation has only ahash as a backend and a fix-sized array of possible algorithms used in parallel. Signed-off-by: Dmitry Safonov <dima@arista.com> --- crypto/Kconfig | 6 + crypto/Makefile | 1 + crypto/crypto_pool.c | 291 ++++++++++++++++++++++++++++++++++++++++++ include/crypto/pool.h | 34 +++++ 4 files changed, 332 insertions(+) create mode 100644 crypto/crypto_pool.c create mode 100644 include/crypto/pool.h