root/drivers/s390/crypto/pkey_base.c
// SPDX-License-Identifier: GPL-2.0
/*
 *  pkey base: debug feature, pkey handler registry
 *
 *  Copyright IBM Corp. 2024
 */

#define pr_fmt(fmt) "pkey: " fmt

#include <linux/cpufeature.h>
#include <linux/export.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/rculist.h>

#include "pkey_base.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("IBM Corporation");
MODULE_DESCRIPTION("s390 protected key base and api");

/*
 * pkey debug feature
 */
debug_info_t *pkey_dbf_info;
EXPORT_SYMBOL(pkey_dbf_info);

/*
 * pkey handler registry
 */

static DEFINE_SPINLOCK(handler_list_write_lock);
static LIST_HEAD(handler_list);

int pkey_handler_register(struct pkey_handler *handler)
{
        const struct pkey_handler *h;

        if (!handler ||
            !handler->is_supported_key ||
            !handler->is_supported_keytype)
                return -EINVAL;

        if (!try_module_get(handler->module))
                return -ENXIO;

        spin_lock(&handler_list_write_lock);

        rcu_read_lock();
        list_for_each_entry_rcu(h, &handler_list, list) {
                if (h == handler) {
                        rcu_read_unlock();
                        spin_unlock(&handler_list_write_lock);
                        module_put(handler->module);
                        return -EEXIST;
                }
        }
        rcu_read_unlock();

        list_add_rcu(&handler->list, &handler_list);
        spin_unlock(&handler_list_write_lock);
        /*
         * Fast path to push the info about the updated list to the other
         * CPUs. If removed, the other CPUs may get the updated list when the
         * RCU context is synched. As this code is in general not performance
         * critical and the list update mostly only occurs at the early time in
         * system startup the focus is on concurrency versus performance.
         */
        synchronize_rcu();

        module_put(handler->module);

        PKEY_DBF_INFO("%s pkey handler '%s' registered\n", __func__,
                      handler->name ?: "<no name>");

        return 0;
}
EXPORT_SYMBOL(pkey_handler_register);

int pkey_handler_unregister(struct pkey_handler *handler)
{
        spin_lock(&handler_list_write_lock);
        list_del_rcu(&handler->list);
        INIT_LIST_HEAD_RCU(&handler->list);
        spin_unlock(&handler_list_write_lock);
        synchronize_rcu();

        PKEY_DBF_INFO("%s pkey handler '%s' unregistered\n", __func__,
                      handler->name ?: "<no name>");

        return 0;
}
EXPORT_SYMBOL(pkey_handler_unregister);

/*
 * Handler invocation functions.
 */

const struct pkey_handler *pkey_handler_get_keybased(const u8 *key, u32 keylen)
{
        const struct pkey_handler *h;

        rcu_read_lock();
        list_for_each_entry_rcu(h, &handler_list, list) {
                if (!try_module_get(h->module))
                        continue;
                if (h->is_supported_key(key, keylen)) {
                        rcu_read_unlock();
                        return h;
                }
                module_put(h->module);
        }
        rcu_read_unlock();

        return NULL;
}
EXPORT_SYMBOL(pkey_handler_get_keybased);

const struct pkey_handler *pkey_handler_get_keytypebased(enum pkey_key_type kt)
{
        const struct pkey_handler *h;

        rcu_read_lock();
        list_for_each_entry_rcu(h, &handler_list, list) {
                if (!try_module_get(h->module))
                        continue;
                if (h->is_supported_keytype(kt)) {
                        rcu_read_unlock();
                        return h;
                }
                module_put(h->module);
        }
        rcu_read_unlock();

        return NULL;
}
EXPORT_SYMBOL(pkey_handler_get_keytypebased);

void pkey_handler_put(const struct pkey_handler *handler)
{
        const struct pkey_handler *h;

        if (!handler)
                return;

        rcu_read_lock();
        list_for_each_entry_rcu(h, &handler_list, list) {
                if (h == handler) {
                        module_put(h->module);
                        break;
                }
        }
        rcu_read_unlock();
}
EXPORT_SYMBOL(pkey_handler_put);

int pkey_handler_key_to_protkey(const struct pkey_apqn *apqns, size_t nr_apqns,
                                const u8 *key, u32 keylen,
                                u8 *protkey, u32 *protkeylen, u32 *protkeytype,
                                u32 xflags)
{
        const struct pkey_handler *h;
        int rc = -ENODEV;

        h = pkey_handler_get_keybased(key, keylen);
        if (h && h->key_to_protkey) {
                rc = h->key_to_protkey(apqns, nr_apqns, key, keylen,
                                       protkey, protkeylen,
                                       protkeytype, xflags);
        }
        pkey_handler_put(h);

        return rc;
}
EXPORT_SYMBOL(pkey_handler_key_to_protkey);

/*
 * This handler invocation is special as there may be more than
 * one handler providing support for the very same key (type).
 * And the handler may not respond true on is_supported_key(),
 * so simple try and check return value here.
 */
int pkey_handler_slowpath_key_to_protkey(const struct pkey_apqn *apqns,
                                         size_t nr_apqns,
                                         const u8 *key, u32 keylen,
                                         u8 *protkey, u32 *protkeylen,
                                         u32 *protkeytype, u32 xflags)
{
        const struct pkey_handler *h, *htmp[10];
        int i, n = 0, rc = -ENODEV;

        rcu_read_lock();
        list_for_each_entry_rcu(h, &handler_list, list) {
                if (!try_module_get(h->module))
                        continue;
                if (h->slowpath_key_to_protkey && n < ARRAY_SIZE(htmp))
                        htmp[n++] = h;
                else
                        module_put(h->module);
        }
        rcu_read_unlock();

        for (i = 0; i < n; i++) {
                h = htmp[i];
                if (rc)
                        rc = h->slowpath_key_to_protkey(apqns, nr_apqns,
                                                        key, keylen,
                                                        protkey, protkeylen,
                                                        protkeytype, xflags);
                module_put(h->module);
        }

        return rc;
}
EXPORT_SYMBOL(pkey_handler_slowpath_key_to_protkey);

int pkey_handler_gen_key(const struct pkey_apqn *apqns, size_t nr_apqns,
                         u32 keytype, u32 keysubtype,
                         u32 keybitsize, u32 flags,
                         u8 *keybuf, u32 *keybuflen, u32 *keyinfo, u32 xflags)
{
        const struct pkey_handler *h;
        int rc = -ENODEV;

        h = pkey_handler_get_keytypebased(keysubtype);
        if (h && h->gen_key) {
                rc = h->gen_key(apqns, nr_apqns, keytype, keysubtype,
                                keybitsize, flags,
                                keybuf, keybuflen, keyinfo, xflags);
        }
        pkey_handler_put(h);

        return rc;
}
EXPORT_SYMBOL(pkey_handler_gen_key);

int pkey_handler_clr_to_key(const struct pkey_apqn *apqns, size_t nr_apqns,
                            u32 keytype, u32 keysubtype,
                            u32 keybitsize, u32 flags,
                            const u8 *clrkey, u32 clrkeylen,
                            u8 *keybuf, u32 *keybuflen, u32 *keyinfo,
                            u32 xflags)
{
        const struct pkey_handler *h;
        int rc = -ENODEV;

        h = pkey_handler_get_keytypebased(keysubtype);
        if (h && h->clr_to_key) {
                rc = h->clr_to_key(apqns, nr_apqns, keytype, keysubtype,
                                   keybitsize, flags, clrkey, clrkeylen,
                                   keybuf, keybuflen, keyinfo, xflags);
        }
        pkey_handler_put(h);

        return rc;
}
EXPORT_SYMBOL(pkey_handler_clr_to_key);

int pkey_handler_verify_key(const u8 *key, u32 keylen,
                            u16 *card, u16 *dom,
                            u32 *keytype, u32 *keybitsize, u32 *flags,
                            u32 xflags)
{
        const struct pkey_handler *h;
        int rc = -ENODEV;

        h = pkey_handler_get_keybased(key, keylen);
        if (h && h->verify_key) {
                rc = h->verify_key(key, keylen, card, dom,
                                   keytype, keybitsize, flags, xflags);
        }
        pkey_handler_put(h);

        return rc;
}
EXPORT_SYMBOL(pkey_handler_verify_key);

int pkey_handler_apqns_for_key(const u8 *key, u32 keylen, u32 flags,
                               struct pkey_apqn *apqns, size_t *nr_apqns,
                               u32 xflags)
{
        const struct pkey_handler *h;
        int rc = -ENODEV;

        h = pkey_handler_get_keybased(key, keylen);
        if (h && h->apqns_for_key)
                rc = h->apqns_for_key(key, keylen, flags, apqns, nr_apqns,
                                      xflags);
        pkey_handler_put(h);

        return rc;
}
EXPORT_SYMBOL(pkey_handler_apqns_for_key);

int pkey_handler_apqns_for_keytype(enum pkey_key_type keysubtype,
                                   u8 cur_mkvp[32], u8 alt_mkvp[32], u32 flags,
                                   struct pkey_apqn *apqns, size_t *nr_apqns,
                                   u32 xflags)
{
        const struct pkey_handler *h;
        int rc = -ENODEV;

        h = pkey_handler_get_keytypebased(keysubtype);
        if (h && h->apqns_for_keytype) {
                rc = h->apqns_for_keytype(keysubtype,
                                          cur_mkvp, alt_mkvp, flags,
                                          apqns, nr_apqns, xflags);
        }
        pkey_handler_put(h);

        return rc;
}
EXPORT_SYMBOL(pkey_handler_apqns_for_keytype);

void pkey_handler_request_modules(void)
{
#ifdef CONFIG_MODULES
        static const char * const pkey_handler_modules[] = {
#if IS_MODULE(CONFIG_PKEY_CCA)
                "pkey_cca",
#endif
#if IS_MODULE(CONFIG_PKEY_EP11)
                "pkey_ep11",
#endif
#if IS_MODULE(CONFIG_PKEY_PCKMO)
                "pkey_pckmo",
#endif
#if IS_MODULE(CONFIG_PKEY_UV)
                "pkey_uv",
#endif
        };
        int i;

        for (i = 0; i < ARRAY_SIZE(pkey_handler_modules); i++) {
                const struct pkey_handler *h;
                bool found = false;

                rcu_read_lock();
                list_for_each_entry_rcu(h, &handler_list, list) {
                        if (h->module &&
                            !strcmp(h->module->name, pkey_handler_modules[i])) {
                                found = true;
                                break;
                        }
                }
                rcu_read_unlock();
                if (!found) {
                        pr_debug("request_module(%s)\n", pkey_handler_modules[i]);
                        request_module(pkey_handler_modules[i]);
                }
        }
#endif
}
EXPORT_SYMBOL(pkey_handler_request_modules);

/*
 * Module init
 */
static int __init pkey_init(void)
{
        int rc;

        /* init debug feature */
        pkey_dbf_info = debug_register("pkey", 1, 1, 5 * sizeof(long));
        debug_register_view(pkey_dbf_info, &debug_sprintf_view);
        debug_set_level(pkey_dbf_info, 4);

        /* the handler registry does not need any init */

        rc = pkey_api_init();
        if (rc)
                debug_unregister(pkey_dbf_info);

        return rc;
}

/*
 * Module exit
 */
static void __exit pkey_exit(void)
{
        pkey_api_exit();
}

module_cpu_feature_match(S390_CPU_FEATURE_MSA, pkey_init);
module_exit(pkey_exit);