| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * pkey cca specific code
- *
- * Copyright IBM Corp. 2024
- */
- #define KMSG_COMPONENT "pkey"
- #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/cpufeature.h>
- #include "zcrypt_api.h"
- #include "zcrypt_ccamisc.h"
- #include "pkey_base.h"
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("IBM Corporation");
- MODULE_DESCRIPTION("s390 protected key CCA handler");
- #if IS_MODULE(CONFIG_PKEY_CCA)
- static struct ap_device_id pkey_cca_card_ids[] = {
- { .dev_type = AP_DEVICE_TYPE_CEX4 },
- { .dev_type = AP_DEVICE_TYPE_CEX5 },
- { .dev_type = AP_DEVICE_TYPE_CEX6 },
- { .dev_type = AP_DEVICE_TYPE_CEX7 },
- { .dev_type = AP_DEVICE_TYPE_CEX8 },
- { /* end of list */ },
- };
- MODULE_DEVICE_TABLE(ap, pkey_cca_card_ids);
- #endif
- /*
- * Check key blob for known and supported CCA key.
- */
- static bool is_cca_key(const u8 *key, u32 keylen)
- {
- struct keytoken_header *hdr = (struct keytoken_header *)key;
- if (keylen < sizeof(*hdr))
- return false;
- switch (hdr->type) {
- case TOKTYPE_CCA_INTERNAL:
- switch (hdr->version) {
- case TOKVER_CCA_AES:
- case TOKVER_CCA_VLSC:
- return true;
- default:
- return false;
- }
- case TOKTYPE_CCA_INTERNAL_PKA:
- return true;
- default:
- return false;
- }
- }
- static bool is_cca_keytype(enum pkey_key_type key_type)
- {
- switch (key_type) {
- case PKEY_TYPE_CCA_DATA:
- case PKEY_TYPE_CCA_CIPHER:
- case PKEY_TYPE_CCA_ECC:
- return true;
- default:
- return false;
- }
- }
- static int cca_apqns4key(const u8 *key, u32 keylen, u32 flags,
- struct pkey_apqn *apqns, size_t *nr_apqns)
- {
- struct keytoken_header *hdr = (struct keytoken_header *)key;
- u32 _nr_apqns, *_apqns = NULL;
- int rc;
- if (!flags)
- flags = PKEY_FLAGS_MATCH_CUR_MKVP | PKEY_FLAGS_MATCH_ALT_MKVP;
- if (keylen < sizeof(struct keytoken_header))
- return -EINVAL;
- zcrypt_wait_api_operational();
- if (hdr->type == TOKTYPE_CCA_INTERNAL) {
- u64 cur_mkvp = 0, old_mkvp = 0;
- int minhwtype = ZCRYPT_CEX3C;
- if (hdr->version == TOKVER_CCA_AES) {
- struct secaeskeytoken *t = (struct secaeskeytoken *)key;
- if (flags & PKEY_FLAGS_MATCH_CUR_MKVP)
- cur_mkvp = t->mkvp;
- if (flags & PKEY_FLAGS_MATCH_ALT_MKVP)
- old_mkvp = t->mkvp;
- } else if (hdr->version == TOKVER_CCA_VLSC) {
- struct cipherkeytoken *t = (struct cipherkeytoken *)key;
- minhwtype = ZCRYPT_CEX6;
- if (flags & PKEY_FLAGS_MATCH_CUR_MKVP)
- cur_mkvp = t->mkvp0;
- if (flags & PKEY_FLAGS_MATCH_ALT_MKVP)
- old_mkvp = t->mkvp0;
- } else {
- /* unknown CCA internal token type */
- return -EINVAL;
- }
- rc = cca_findcard2(&_apqns, &_nr_apqns, 0xFFFF, 0xFFFF,
- minhwtype, AES_MK_SET,
- cur_mkvp, old_mkvp, 1);
- if (rc)
- goto out;
- } else if (hdr->type == TOKTYPE_CCA_INTERNAL_PKA) {
- struct eccprivkeytoken *t = (struct eccprivkeytoken *)key;
- u64 cur_mkvp = 0, old_mkvp = 0;
- if (t->secid == 0x20) {
- if (flags & PKEY_FLAGS_MATCH_CUR_MKVP)
- cur_mkvp = t->mkvp;
- if (flags & PKEY_FLAGS_MATCH_ALT_MKVP)
- old_mkvp = t->mkvp;
- } else {
- /* unknown CCA internal 2 token type */
- return -EINVAL;
- }
- rc = cca_findcard2(&_apqns, &_nr_apqns, 0xFFFF, 0xFFFF,
- ZCRYPT_CEX7, APKA_MK_SET,
- cur_mkvp, old_mkvp, 1);
- if (rc)
- goto out;
- } else {
- PKEY_DBF_ERR("%s unknown/unsupported blob type %d version %d\n",
- __func__, hdr->type, hdr->version);
- return -EINVAL;
- }
- if (apqns) {
- if (*nr_apqns < _nr_apqns)
- rc = -ENOSPC;
- else
- memcpy(apqns, _apqns, _nr_apqns * sizeof(u32));
- }
- *nr_apqns = _nr_apqns;
- out:
- kfree(_apqns);
- pr_debug("rc=%d\n", rc);
- return rc;
- }
- static int cca_apqns4type(enum pkey_key_type ktype,
- u8 cur_mkvp[32], u8 alt_mkvp[32], u32 flags,
- struct pkey_apqn *apqns, size_t *nr_apqns)
- {
- u32 _nr_apqns, *_apqns = NULL;
- int rc;
- zcrypt_wait_api_operational();
- if (ktype == PKEY_TYPE_CCA_DATA || ktype == PKEY_TYPE_CCA_CIPHER) {
- u64 cur_mkvp = 0, old_mkvp = 0;
- int minhwtype = ZCRYPT_CEX3C;
- if (flags & PKEY_FLAGS_MATCH_CUR_MKVP)
- cur_mkvp = *((u64 *)cur_mkvp);
- if (flags & PKEY_FLAGS_MATCH_ALT_MKVP)
- old_mkvp = *((u64 *)alt_mkvp);
- if (ktype == PKEY_TYPE_CCA_CIPHER)
- minhwtype = ZCRYPT_CEX6;
- rc = cca_findcard2(&_apqns, &_nr_apqns, 0xFFFF, 0xFFFF,
- minhwtype, AES_MK_SET,
- cur_mkvp, old_mkvp, 1);
- if (rc)
- goto out;
- } else if (ktype == PKEY_TYPE_CCA_ECC) {
- u64 cur_mkvp = 0, old_mkvp = 0;
- if (flags & PKEY_FLAGS_MATCH_CUR_MKVP)
- cur_mkvp = *((u64 *)cur_mkvp);
- if (flags & PKEY_FLAGS_MATCH_ALT_MKVP)
- old_mkvp = *((u64 *)alt_mkvp);
- rc = cca_findcard2(&_apqns, &_nr_apqns, 0xFFFF, 0xFFFF,
- ZCRYPT_CEX7, APKA_MK_SET,
- cur_mkvp, old_mkvp, 1);
- if (rc)
- goto out;
- } else {
- PKEY_DBF_ERR("%s unknown/unsupported key type %d",
- __func__, (int)ktype);
- return -EINVAL;
- }
- if (apqns) {
- if (*nr_apqns < _nr_apqns)
- rc = -ENOSPC;
- else
- memcpy(apqns, _apqns, _nr_apqns * sizeof(u32));
- }
- *nr_apqns = _nr_apqns;
- out:
- kfree(_apqns);
- pr_debug("rc=%d\n", rc);
- return rc;
- }
- static int cca_key2protkey(const struct pkey_apqn *apqns, size_t nr_apqns,
- const u8 *key, u32 keylen,
- u8 *protkey, u32 *protkeylen, u32 *protkeytype)
- {
- struct keytoken_header *hdr = (struct keytoken_header *)key;
- struct pkey_apqn *local_apqns = NULL;
- int i, rc;
- if (keylen < sizeof(*hdr))
- return -EINVAL;
- if (hdr->type == TOKTYPE_CCA_INTERNAL &&
- hdr->version == TOKVER_CCA_AES) {
- /* CCA AES data key */
- if (keylen != sizeof(struct secaeskeytoken))
- return -EINVAL;
- if (cca_check_secaeskeytoken(pkey_dbf_info, 3, key, 0))
- return -EINVAL;
- } else if (hdr->type == TOKTYPE_CCA_INTERNAL &&
- hdr->version == TOKVER_CCA_VLSC) {
- /* CCA AES cipher key */
- if (keylen < hdr->len || keylen > MAXCCAVLSCTOKENSIZE)
- return -EINVAL;
- if (cca_check_secaescipherkey(pkey_dbf_info,
- 3, key, 0, 1))
- return -EINVAL;
- } else if (hdr->type == TOKTYPE_CCA_INTERNAL_PKA) {
- /* CCA ECC (private) key */
- if (keylen < sizeof(struct eccprivkeytoken))
- return -EINVAL;
- if (cca_check_sececckeytoken(pkey_dbf_info, 3, key, keylen, 1))
- return -EINVAL;
- } else {
- PKEY_DBF_ERR("%s unknown/unsupported blob type %d version %d\n",
- __func__, hdr->type, hdr->version);
- return -EINVAL;
- }
- zcrypt_wait_api_operational();
- if (!apqns || (nr_apqns == 1 &&
- apqns[0].card == 0xFFFF && apqns[0].domain == 0xFFFF)) {
- nr_apqns = MAXAPQNSINLIST;
- local_apqns = kmalloc_array(nr_apqns, sizeof(struct pkey_apqn),
- GFP_KERNEL);
- if (!local_apqns)
- return -ENOMEM;
- rc = cca_apqns4key(key, keylen, 0, local_apqns, &nr_apqns);
- if (rc)
- goto out;
- apqns = local_apqns;
- }
- for (rc = -ENODEV, i = 0; rc && i < nr_apqns; i++) {
- if (hdr->type == TOKTYPE_CCA_INTERNAL &&
- hdr->version == TOKVER_CCA_AES) {
- rc = cca_sec2protkey(apqns[i].card, apqns[i].domain,
- key, protkey,
- protkeylen, protkeytype);
- } else if (hdr->type == TOKTYPE_CCA_INTERNAL &&
- hdr->version == TOKVER_CCA_VLSC) {
- rc = cca_cipher2protkey(apqns[i].card, apqns[i].domain,
- key, protkey,
- protkeylen, protkeytype);
- } else if (hdr->type == TOKTYPE_CCA_INTERNAL_PKA) {
- rc = cca_ecc2protkey(apqns[i].card, apqns[i].domain,
- key, protkey,
- protkeylen, protkeytype);
- } else {
- rc = -EINVAL;
- break;
- }
- }
- out:
- kfree(local_apqns);
- pr_debug("rc=%d\n", rc);
- return rc;
- }
- /*
- * Generate CCA secure key.
- * As of now only CCA AES Data or Cipher secure keys are
- * supported.
- * keytype is one of the PKEY_KEYTYPE_* constants,
- * subtype may be 0 or PKEY_TYPE_CCA_DATA or PKEY_TYPE_CCA_CIPHER,
- * keybitsize is the bit size of the key (may be 0 for
- * keytype PKEY_KEYTYPE_AES_*).
- */
- static int cca_gen_key(const struct pkey_apqn *apqns, size_t nr_apqns,
- u32 keytype, u32 subtype,
- u32 keybitsize, u32 flags,
- u8 *keybuf, u32 *keybuflen, u32 *_keyinfo)
- {
- struct pkey_apqn *local_apqns = NULL;
- int i, len, rc;
- /* check keytype, subtype, keybitsize */
- switch (keytype) {
- case PKEY_KEYTYPE_AES_128:
- case PKEY_KEYTYPE_AES_192:
- case PKEY_KEYTYPE_AES_256:
- len = pkey_keytype_aes_to_size(keytype);
- if (keybitsize && keybitsize != 8 * len) {
- PKEY_DBF_ERR("%s unknown/unsupported keybitsize %d\n",
- __func__, keybitsize);
- return -EINVAL;
- }
- keybitsize = 8 * len;
- switch (subtype) {
- case PKEY_TYPE_CCA_DATA:
- case PKEY_TYPE_CCA_CIPHER:
- break;
- default:
- PKEY_DBF_ERR("%s unknown/unsupported subtype %d\n",
- __func__, subtype);
- return -EINVAL;
- }
- break;
- default:
- PKEY_DBF_ERR("%s unknown/unsupported keytype %d\n",
- __func__, keytype);
- return -EINVAL;
- }
- zcrypt_wait_api_operational();
- if (!apqns || (nr_apqns == 1 &&
- apqns[0].card == 0xFFFF && apqns[0].domain == 0xFFFF)) {
- nr_apqns = MAXAPQNSINLIST;
- local_apqns = kmalloc_array(nr_apqns, sizeof(struct pkey_apqn),
- GFP_KERNEL);
- if (!local_apqns)
- return -ENOMEM;
- rc = cca_apqns4type(subtype, NULL, NULL, 0,
- local_apqns, &nr_apqns);
- if (rc)
- goto out;
- apqns = local_apqns;
- }
- for (rc = -ENODEV, i = 0; rc && i < nr_apqns; i++) {
- if (subtype == PKEY_TYPE_CCA_CIPHER) {
- rc = cca_gencipherkey(apqns[i].card, apqns[i].domain,
- keybitsize, flags,
- keybuf, keybuflen);
- } else {
- /* PKEY_TYPE_CCA_DATA */
- rc = cca_genseckey(apqns[i].card, apqns[i].domain,
- keybitsize, keybuf);
- *keybuflen = (rc ? 0 : SECKEYBLOBSIZE);
- }
- }
- out:
- kfree(local_apqns);
- pr_debug("rc=%d\n", rc);
- return rc;
- }
- /*
- * Generate CCA secure key with given clear key value.
- * As of now only CCA AES Data or Cipher secure keys are
- * supported.
- * keytype is one of the PKEY_KEYTYPE_* constants,
- * subtype may be 0 or PKEY_TYPE_CCA_DATA or PKEY_TYPE_CCA_CIPHER,
- * keybitsize is the bit size of the key (may be 0 for
- * keytype PKEY_KEYTYPE_AES_*).
- */
- static int cca_clr2key(const struct pkey_apqn *apqns, size_t nr_apqns,
- u32 keytype, u32 subtype,
- u32 keybitsize, u32 flags,
- const u8 *clrkey, u32 clrkeylen,
- u8 *keybuf, u32 *keybuflen, u32 *_keyinfo)
- {
- struct pkey_apqn *local_apqns = NULL;
- int i, len, rc;
- /* check keytype, subtype, clrkeylen, keybitsize */
- switch (keytype) {
- case PKEY_KEYTYPE_AES_128:
- case PKEY_KEYTYPE_AES_192:
- case PKEY_KEYTYPE_AES_256:
- len = pkey_keytype_aes_to_size(keytype);
- if (keybitsize && keybitsize != 8 * len) {
- PKEY_DBF_ERR("%s unknown/unsupported keybitsize %d\n",
- __func__, keybitsize);
- return -EINVAL;
- }
- keybitsize = 8 * len;
- if (clrkeylen != len) {
- PKEY_DBF_ERR("%s invalid clear key len %d != %d\n",
- __func__, clrkeylen, len);
- return -EINVAL;
- }
- switch (subtype) {
- case PKEY_TYPE_CCA_DATA:
- case PKEY_TYPE_CCA_CIPHER:
- break;
- default:
- PKEY_DBF_ERR("%s unknown/unsupported subtype %d\n",
- __func__, subtype);
- return -EINVAL;
- }
- break;
- default:
- PKEY_DBF_ERR("%s unknown/unsupported keytype %d\n",
- __func__, keytype);
- return -EINVAL;
- }
- zcrypt_wait_api_operational();
- if (!apqns || (nr_apqns == 1 &&
- apqns[0].card == 0xFFFF && apqns[0].domain == 0xFFFF)) {
- nr_apqns = MAXAPQNSINLIST;
- local_apqns = kmalloc_array(nr_apqns, sizeof(struct pkey_apqn),
- GFP_KERNEL);
- if (!local_apqns)
- return -ENOMEM;
- rc = cca_apqns4type(subtype, NULL, NULL, 0,
- local_apqns, &nr_apqns);
- if (rc)
- goto out;
- apqns = local_apqns;
- }
- for (rc = -ENODEV, i = 0; rc && i < nr_apqns; i++) {
- if (subtype == PKEY_TYPE_CCA_CIPHER) {
- rc = cca_clr2cipherkey(apqns[i].card, apqns[i].domain,
- keybitsize, flags, clrkey,
- keybuf, keybuflen);
- } else {
- /* PKEY_TYPE_CCA_DATA */
- rc = cca_clr2seckey(apqns[i].card, apqns[i].domain,
- keybitsize, clrkey, keybuf);
- *keybuflen = (rc ? 0 : SECKEYBLOBSIZE);
- }
- }
- out:
- kfree(local_apqns);
- pr_debug("rc=%d\n", rc);
- return rc;
- }
- static int cca_verifykey(const u8 *key, u32 keylen,
- u16 *card, u16 *dom,
- u32 *keytype, u32 *keybitsize, u32 *flags)
- {
- struct keytoken_header *hdr = (struct keytoken_header *)key;
- u32 nr_apqns, *apqns = NULL;
- int rc;
- if (keylen < sizeof(*hdr))
- return -EINVAL;
- zcrypt_wait_api_operational();
- if (hdr->type == TOKTYPE_CCA_INTERNAL &&
- hdr->version == TOKVER_CCA_AES) {
- struct secaeskeytoken *t = (struct secaeskeytoken *)key;
- rc = cca_check_secaeskeytoken(pkey_dbf_info, 3, key, 0);
- if (rc)
- goto out;
- *keytype = PKEY_TYPE_CCA_DATA;
- *keybitsize = t->bitsize;
- rc = cca_findcard2(&apqns, &nr_apqns, *card, *dom,
- ZCRYPT_CEX3C, AES_MK_SET,
- t->mkvp, 0, 1);
- if (!rc)
- *flags = PKEY_FLAGS_MATCH_CUR_MKVP;
- if (rc == -ENODEV) {
- rc = cca_findcard2(&apqns, &nr_apqns, *card, *dom,
- ZCRYPT_CEX3C, AES_MK_SET,
- 0, t->mkvp, 1);
- if (!rc)
- *flags = PKEY_FLAGS_MATCH_ALT_MKVP;
- }
- if (rc)
- goto out;
- *card = ((struct pkey_apqn *)apqns)->card;
- *dom = ((struct pkey_apqn *)apqns)->domain;
- } else if (hdr->type == TOKTYPE_CCA_INTERNAL &&
- hdr->version == TOKVER_CCA_VLSC) {
- struct cipherkeytoken *t = (struct cipherkeytoken *)key;
- rc = cca_check_secaescipherkey(pkey_dbf_info, 3, key, 0, 1);
- if (rc)
- goto out;
- *keytype = PKEY_TYPE_CCA_CIPHER;
- *keybitsize = PKEY_SIZE_UNKNOWN;
- if (!t->plfver && t->wpllen == 512)
- *keybitsize = PKEY_SIZE_AES_128;
- else if (!t->plfver && t->wpllen == 576)
- *keybitsize = PKEY_SIZE_AES_192;
- else if (!t->plfver && t->wpllen == 640)
- *keybitsize = PKEY_SIZE_AES_256;
- rc = cca_findcard2(&apqns, &nr_apqns, *card, *dom,
- ZCRYPT_CEX6, AES_MK_SET,
- t->mkvp0, 0, 1);
- if (!rc)
- *flags = PKEY_FLAGS_MATCH_CUR_MKVP;
- if (rc == -ENODEV) {
- rc = cca_findcard2(&apqns, &nr_apqns, *card, *dom,
- ZCRYPT_CEX6, AES_MK_SET,
- 0, t->mkvp0, 1);
- if (!rc)
- *flags = PKEY_FLAGS_MATCH_ALT_MKVP;
- }
- if (rc)
- goto out;
- *card = ((struct pkey_apqn *)apqns)->card;
- *dom = ((struct pkey_apqn *)apqns)->domain;
- } else {
- /* unknown/unsupported key blob */
- rc = -EINVAL;
- }
- out:
- kfree(apqns);
- pr_debug("rc=%d\n", rc);
- return rc;
- }
- /*
- * This function provides an alternate but usually slow way
- * to convert a 'clear key token' with AES key material into
- * a protected key. This is done via an intermediate step
- * which creates a CCA AES DATA secure key first and then
- * derives the protected key from this secure key.
- */
- static int cca_slowpath_key2protkey(const struct pkey_apqn *apqns,
- size_t nr_apqns,
- const u8 *key, u32 keylen,
- u8 *protkey, u32 *protkeylen,
- u32 *protkeytype)
- {
- const struct keytoken_header *hdr = (const struct keytoken_header *)key;
- const struct clearkeytoken *t = (const struct clearkeytoken *)key;
- u32 tmplen, keysize = 0;
- u8 *tmpbuf;
- int i, rc;
- if (keylen < sizeof(*hdr))
- return -EINVAL;
- if (hdr->type == TOKTYPE_NON_CCA &&
- hdr->version == TOKVER_CLEAR_KEY)
- keysize = pkey_keytype_aes_to_size(t->keytype);
- if (!keysize || t->len != keysize)
- return -EINVAL;
- /* alloc tmp key buffer */
- tmpbuf = kmalloc(SECKEYBLOBSIZE, GFP_ATOMIC);
- if (!tmpbuf)
- return -ENOMEM;
- /* try two times in case of failure */
- for (i = 0, rc = -ENODEV; i < 2 && rc; i++) {
- tmplen = SECKEYBLOBSIZE;
- rc = cca_clr2key(NULL, 0, t->keytype, PKEY_TYPE_CCA_DATA,
- 8 * keysize, 0, t->clearkey, t->len,
- tmpbuf, &tmplen, NULL);
- pr_debug("cca_clr2key()=%d\n", rc);
- if (rc)
- continue;
- rc = cca_key2protkey(NULL, 0, tmpbuf, tmplen,
- protkey, protkeylen, protkeytype);
- pr_debug("cca_key2protkey()=%d\n", rc);
- }
- kfree(tmpbuf);
- pr_debug("rc=%d\n", rc);
- return rc;
- }
- static struct pkey_handler cca_handler = {
- .module = THIS_MODULE,
- .name = "PKEY CCA handler",
- .is_supported_key = is_cca_key,
- .is_supported_keytype = is_cca_keytype,
- .key_to_protkey = cca_key2protkey,
- .slowpath_key_to_protkey = cca_slowpath_key2protkey,
- .gen_key = cca_gen_key,
- .clr_to_key = cca_clr2key,
- .verify_key = cca_verifykey,
- .apqns_for_key = cca_apqns4key,
- .apqns_for_keytype = cca_apqns4type,
- };
- /*
- * Module init
- */
- static int __init pkey_cca_init(void)
- {
- /* register this module as pkey handler for all the cca stuff */
- return pkey_handler_register(&cca_handler);
- }
- /*
- * Module exit
- */
- static void __exit pkey_cca_exit(void)
- {
- /* unregister this module as pkey handler */
- pkey_handler_unregister(&cca_handler);
- }
- module_init(pkey_cca_init);
- module_exit(pkey_cca_exit);
|