diff options
Diffstat (limited to 'crypto/algapi.c')
-rw-r--r-- | crypto/algapi.c | 185 |
1 files changed, 169 insertions, 16 deletions
diff --git a/crypto/algapi.c b/crypto/algapi.c index acea250..36c4f1bd 100644 --- a/crypto/algapi.c +++ b/crypto/algapi.c @@ -10,6 +10,7 @@ * */ +#include <linux/err.h> #include <linux/errno.h> #include <linux/init.h> #include <linux/kernel.h> @@ -73,27 +74,96 @@ static int crypto_check_alg(struct crypto_alg *alg) return crypto_set_driver_name(alg); } -static int __crypto_register_alg(struct crypto_alg *alg) +static void crypto_destroy_instance(struct crypto_alg *alg) +{ + struct crypto_instance *inst = (void *)alg; + struct crypto_template *tmpl = inst->tmpl; + + tmpl->free(inst); + crypto_tmpl_put(tmpl); +} + +static void crypto_remove_spawns(struct list_head *spawns, + struct list_head *list) +{ + struct crypto_spawn *spawn, *n; + + list_for_each_entry_safe(spawn, n, spawns, list) { + struct crypto_instance *inst = spawn->inst; + struct crypto_template *tmpl = inst->tmpl; + + list_del_init(&spawn->list); + spawn->alg = NULL; + + if (crypto_is_dead(&inst->alg)) + continue; + + inst->alg.cra_flags |= CRYPTO_ALG_DEAD; + if (!tmpl || !crypto_tmpl_get(tmpl)) + continue; + + crypto_notify(CRYPTO_MSG_ALG_UNREGISTER, &inst->alg); + list_move(&inst->alg.cra_list, list); + hlist_del(&inst->list); + inst->alg.cra_destroy = crypto_destroy_instance; + + if (!list_empty(&inst->alg.cra_users)) { + if (&n->list == spawns) + n = list_entry(inst->alg.cra_users.next, + typeof(*n), list); + __list_splice(&inst->alg.cra_users, spawns->prev); + } + } +} + +static int __crypto_register_alg(struct crypto_alg *alg, + struct list_head *list) { struct crypto_alg *q; - int ret = -EEXIST; + int ret = -EAGAIN; + + if (crypto_is_dead(alg)) + goto out; + + INIT_LIST_HEAD(&alg->cra_users); + + ret = -EEXIST; atomic_set(&alg->cra_refcnt, 1); list_for_each_entry(q, &crypto_alg_list, cra_list) { if (q == alg) goto out; - if (crypto_is_larval(q) && - (!strcmp(alg->cra_name, q->cra_name) || - !strcmp(alg->cra_driver_name, q->cra_name))) { + + if (crypto_is_moribund(q)) + continue; + + if (crypto_is_larval(q)) { struct crypto_larval *larval = (void *)q; + if (strcmp(alg->cra_name, q->cra_name) && + strcmp(alg->cra_driver_name, q->cra_name)) + continue; + + if (larval->adult) + continue; if ((q->cra_flags ^ alg->cra_flags) & larval->mask) continue; if (!crypto_mod_get(alg)) continue; + larval->adult = alg; complete(&larval->completion); + continue; } + + if (strcmp(alg->cra_name, q->cra_name)) + continue; + + if (strcmp(alg->cra_driver_name, q->cra_driver_name) && + q->cra_priority > alg->cra_priority) + continue; + + crypto_remove_spawns(&q->cra_users, list); } list_add(&alg->cra_list, &crypto_alg_list); @@ -105,8 +175,20 @@ out: return ret; } +static void crypto_remove_final(struct list_head *list) +{ + struct crypto_alg *alg; + struct crypto_alg *n; + + list_for_each_entry_safe(alg, n, list, cra_list) { + list_del_init(&alg->cra_list); + crypto_alg_put(alg); + } +} + int crypto_register_alg(struct crypto_alg *alg) { + LIST_HEAD(list); int err; err = crypto_check_alg(alg); @@ -114,23 +196,35 @@ int crypto_register_alg(struct crypto_alg *alg) return err; down_write(&crypto_alg_sem); - err = __crypto_register_alg(alg); + err = __crypto_register_alg(alg, &list); up_write(&crypto_alg_sem); + crypto_remove_final(&list); return err; } EXPORT_SYMBOL_GPL(crypto_register_alg); +static int crypto_remove_alg(struct crypto_alg *alg, struct list_head *list) +{ + if (unlikely(list_empty(&alg->cra_list))) + return -ENOENT; + + alg->cra_flags |= CRYPTO_ALG_DEAD; + + crypto_notify(CRYPTO_MSG_ALG_UNREGISTER, alg); + list_del_init(&alg->cra_list); + crypto_remove_spawns(&alg->cra_users, list); + + return 0; +} + int crypto_unregister_alg(struct crypto_alg *alg) { - int ret = -ENOENT; + int ret; + LIST_HEAD(list); down_write(&crypto_alg_sem); - if (likely(!list_empty(&alg->cra_list))) { - list_del_init(&alg->cra_list); - ret = 0; - } - crypto_notify(CRYPTO_MSG_ALG_UNREGISTER, alg); + ret = crypto_remove_alg(alg, &list); up_write(&crypto_alg_sem); if (ret) @@ -140,6 +234,7 @@ int crypto_unregister_alg(struct crypto_alg *alg) if (alg->cra_destroy) alg->cra_destroy(alg); + crypto_remove_final(&list); return 0; } EXPORT_SYMBOL_GPL(crypto_unregister_alg); @@ -170,6 +265,7 @@ void crypto_unregister_template(struct crypto_template *tmpl) struct crypto_instance *inst; struct hlist_node *p, *n; struct hlist_head *list; + LIST_HEAD(users); down_write(&crypto_alg_sem); @@ -178,9 +274,8 @@ void crypto_unregister_template(struct crypto_template *tmpl) list = &tmpl->instances; hlist_for_each_entry(inst, p, list, list) { - BUG_ON(list_empty(&inst->alg.cra_list)); - list_del_init(&inst->alg.cra_list); - crypto_notify(CRYPTO_MSG_ALG_UNREGISTER, &inst->alg); + int err = crypto_remove_alg(&inst->alg, &users); + BUG_ON(err); } crypto_notify(CRYPTO_MSG_TMPL_UNREGISTER, tmpl); @@ -191,6 +286,7 @@ void crypto_unregister_template(struct crypto_template *tmpl) BUG_ON(atomic_read(&inst->alg.cra_refcnt) != 1); tmpl->free(inst); } + crypto_remove_final(&users); } EXPORT_SYMBOL_GPL(crypto_unregister_template); @@ -222,6 +318,7 @@ EXPORT_SYMBOL_GPL(crypto_lookup_template); int crypto_register_instance(struct crypto_template *tmpl, struct crypto_instance *inst) { + LIST_HEAD(list); int err = -EINVAL; if (inst->alg.cra_destroy) @@ -235,7 +332,7 @@ int crypto_register_instance(struct crypto_template *tmpl, down_write(&crypto_alg_sem); - err = __crypto_register_alg(&inst->alg); + err = __crypto_register_alg(&inst->alg, &list); if (err) goto unlock; @@ -245,11 +342,67 @@ int crypto_register_instance(struct crypto_template *tmpl, unlock: up_write(&crypto_alg_sem); + crypto_remove_final(&list); + err: return err; } EXPORT_SYMBOL_GPL(crypto_register_instance); +int crypto_init_spawn(struct crypto_spawn *spawn, struct crypto_alg *alg, + struct crypto_instance *inst) +{ + int err = -EAGAIN; + + spawn->inst = inst; + + down_write(&crypto_alg_sem); + if (!crypto_is_moribund(alg)) { + list_add(&spawn->list, &alg->cra_users); + spawn->alg = alg; + err = 0; + } + up_write(&crypto_alg_sem); + + return err; +} +EXPORT_SYMBOL_GPL(crypto_init_spawn); + +void crypto_drop_spawn(struct crypto_spawn *spawn) +{ + down_write(&crypto_alg_sem); + list_del(&spawn->list); + up_write(&crypto_alg_sem); +} +EXPORT_SYMBOL_GPL(crypto_drop_spawn); + +struct crypto_tfm *crypto_spawn_tfm(struct crypto_spawn *spawn) +{ + struct crypto_alg *alg; + struct crypto_alg *alg2; + struct crypto_tfm *tfm; + + down_read(&crypto_alg_sem); + alg = spawn->alg; + alg2 = alg; + if (alg2) + alg2 = crypto_mod_get(alg2); + up_read(&crypto_alg_sem); + + if (!alg2) { + if (alg) + crypto_shoot_alg(alg); + return ERR_PTR(-EAGAIN); + } + + tfm = __crypto_alloc_tfm(alg, 0); + if (IS_ERR(tfm)) + crypto_mod_put(alg); + + return tfm; +} +EXPORT_SYMBOL_GPL(crypto_spawn_tfm); + int crypto_register_notifier(struct notifier_block *nb) { return blocking_notifier_chain_register(&crypto_chain, nb); |