|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Xilinx ZynqMP AES Driver. | 
|  | * Copyright (c) 2020 Xilinx Inc. | 
|  | */ | 
|  |  | 
|  | #include <crypto/aes.h> | 
|  | #include <crypto/engine.h> | 
|  | #include <crypto/gcm.h> | 
|  | #include <crypto/internal/aead.h> | 
|  | #include <crypto/scatterwalk.h> | 
|  |  | 
|  | #include <linux/dma-mapping.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/of_device.h> | 
|  | #include <linux/platform_device.h> | 
|  |  | 
|  | #include <linux/firmware/xlnx-zynqmp.h> | 
|  |  | 
|  | #define ZYNQMP_DMA_BIT_MASK	32U | 
|  |  | 
|  | #define ZYNQMP_AES_KEY_SIZE		AES_KEYSIZE_256 | 
|  | #define ZYNQMP_AES_AUTH_SIZE		16U | 
|  | #define ZYNQMP_KEY_SRC_SEL_KEY_LEN	1U | 
|  | #define ZYNQMP_AES_BLK_SIZE		1U | 
|  | #define ZYNQMP_AES_MIN_INPUT_BLK_SIZE	4U | 
|  | #define ZYNQMP_AES_WORD_LEN		4U | 
|  |  | 
|  | #define ZYNQMP_AES_GCM_TAG_MISMATCH_ERR		0x01 | 
|  | #define ZYNQMP_AES_WRONG_KEY_SRC_ERR		0x13 | 
|  | #define ZYNQMP_AES_PUF_NOT_PROGRAMMED		0xE300 | 
|  |  | 
|  | enum zynqmp_aead_op { | 
|  | ZYNQMP_AES_DECRYPT = 0, | 
|  | ZYNQMP_AES_ENCRYPT | 
|  | }; | 
|  |  | 
|  | enum zynqmp_aead_keysrc { | 
|  | ZYNQMP_AES_KUP_KEY = 0, | 
|  | ZYNQMP_AES_DEV_KEY, | 
|  | ZYNQMP_AES_PUF_KEY | 
|  | }; | 
|  |  | 
|  | struct zynqmp_aead_drv_ctx { | 
|  | union { | 
|  | struct aead_alg aead; | 
|  | } alg; | 
|  | struct device *dev; | 
|  | struct crypto_engine *engine; | 
|  | }; | 
|  |  | 
|  | struct zynqmp_aead_hw_req { | 
|  | u64 src; | 
|  | u64 iv; | 
|  | u64 key; | 
|  | u64 dst; | 
|  | u64 size; | 
|  | u64 op; | 
|  | u64 keysrc; | 
|  | }; | 
|  |  | 
|  | struct zynqmp_aead_tfm_ctx { | 
|  | struct crypto_engine_ctx engine_ctx; | 
|  | struct device *dev; | 
|  | u8 key[ZYNQMP_AES_KEY_SIZE]; | 
|  | u8 *iv; | 
|  | u32 keylen; | 
|  | u32 authsize; | 
|  | enum zynqmp_aead_keysrc keysrc; | 
|  | struct crypto_aead *fbk_cipher; | 
|  | }; | 
|  |  | 
|  | struct zynqmp_aead_req_ctx { | 
|  | enum zynqmp_aead_op op; | 
|  | }; | 
|  |  | 
|  | static int zynqmp_aes_aead_cipher(struct aead_request *req) | 
|  | { | 
|  | struct crypto_aead *aead = crypto_aead_reqtfm(req); | 
|  | struct zynqmp_aead_tfm_ctx *tfm_ctx = crypto_aead_ctx(aead); | 
|  | struct zynqmp_aead_req_ctx *rq_ctx = aead_request_ctx(req); | 
|  | struct device *dev = tfm_ctx->dev; | 
|  | struct zynqmp_aead_hw_req *hwreq; | 
|  | dma_addr_t dma_addr_data, dma_addr_hw_req; | 
|  | unsigned int data_size; | 
|  | unsigned int status; | 
|  | int ret; | 
|  | size_t dma_size; | 
|  | char *kbuf; | 
|  | int err; | 
|  |  | 
|  | if (tfm_ctx->keysrc == ZYNQMP_AES_KUP_KEY) | 
|  | dma_size = req->cryptlen + ZYNQMP_AES_KEY_SIZE | 
|  | + GCM_AES_IV_SIZE; | 
|  | else | 
|  | dma_size = req->cryptlen + GCM_AES_IV_SIZE; | 
|  |  | 
|  | kbuf = dma_alloc_coherent(dev, dma_size, &dma_addr_data, GFP_KERNEL); | 
|  | if (!kbuf) | 
|  | return -ENOMEM; | 
|  |  | 
|  | hwreq = dma_alloc_coherent(dev, sizeof(struct zynqmp_aead_hw_req), | 
|  | &dma_addr_hw_req, GFP_KERNEL); | 
|  | if (!hwreq) { | 
|  | dma_free_coherent(dev, dma_size, kbuf, dma_addr_data); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | data_size = req->cryptlen; | 
|  | scatterwalk_map_and_copy(kbuf, req->src, 0, req->cryptlen, 0); | 
|  | memcpy(kbuf + data_size, req->iv, GCM_AES_IV_SIZE); | 
|  |  | 
|  | hwreq->src = dma_addr_data; | 
|  | hwreq->dst = dma_addr_data; | 
|  | hwreq->iv = hwreq->src + data_size; | 
|  | hwreq->keysrc = tfm_ctx->keysrc; | 
|  | hwreq->op = rq_ctx->op; | 
|  |  | 
|  | if (hwreq->op == ZYNQMP_AES_ENCRYPT) | 
|  | hwreq->size = data_size; | 
|  | else | 
|  | hwreq->size = data_size - ZYNQMP_AES_AUTH_SIZE; | 
|  |  | 
|  | if (hwreq->keysrc == ZYNQMP_AES_KUP_KEY) { | 
|  | memcpy(kbuf + data_size + GCM_AES_IV_SIZE, | 
|  | tfm_ctx->key, ZYNQMP_AES_KEY_SIZE); | 
|  |  | 
|  | hwreq->key = hwreq->src + data_size + GCM_AES_IV_SIZE; | 
|  | } else { | 
|  | hwreq->key = 0; | 
|  | } | 
|  |  | 
|  | ret = zynqmp_pm_aes_engine(dma_addr_hw_req, &status); | 
|  |  | 
|  | if (ret) { | 
|  | dev_err(dev, "ERROR: AES PM API failed\n"); | 
|  | err = ret; | 
|  | } else if (status) { | 
|  | switch (status) { | 
|  | case ZYNQMP_AES_GCM_TAG_MISMATCH_ERR: | 
|  | dev_err(dev, "ERROR: Gcm Tag mismatch\n"); | 
|  | break; | 
|  | case ZYNQMP_AES_WRONG_KEY_SRC_ERR: | 
|  | dev_err(dev, "ERROR: Wrong KeySrc, enable secure mode\n"); | 
|  | break; | 
|  | case ZYNQMP_AES_PUF_NOT_PROGRAMMED: | 
|  | dev_err(dev, "ERROR: PUF is not registered\n"); | 
|  | break; | 
|  | default: | 
|  | dev_err(dev, "ERROR: Unknown error\n"); | 
|  | break; | 
|  | } | 
|  | err = -status; | 
|  | } else { | 
|  | if (hwreq->op == ZYNQMP_AES_ENCRYPT) | 
|  | data_size = data_size + ZYNQMP_AES_AUTH_SIZE; | 
|  | else | 
|  | data_size = data_size - ZYNQMP_AES_AUTH_SIZE; | 
|  |  | 
|  | sg_copy_from_buffer(req->dst, sg_nents(req->dst), | 
|  | kbuf, data_size); | 
|  | err = 0; | 
|  | } | 
|  |  | 
|  | if (kbuf) { | 
|  | memzero_explicit(kbuf, dma_size); | 
|  | dma_free_coherent(dev, dma_size, kbuf, dma_addr_data); | 
|  | } | 
|  | if (hwreq) { | 
|  | memzero_explicit(hwreq, sizeof(struct zynqmp_aead_hw_req)); | 
|  | dma_free_coherent(dev, sizeof(struct zynqmp_aead_hw_req), | 
|  | hwreq, dma_addr_hw_req); | 
|  | } | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int zynqmp_fallback_check(struct zynqmp_aead_tfm_ctx *tfm_ctx, | 
|  | struct aead_request *req) | 
|  | { | 
|  | int need_fallback = 0; | 
|  | struct zynqmp_aead_req_ctx *rq_ctx = aead_request_ctx(req); | 
|  |  | 
|  | if (tfm_ctx->authsize != ZYNQMP_AES_AUTH_SIZE) | 
|  | need_fallback = 1; | 
|  |  | 
|  | if (tfm_ctx->keysrc == ZYNQMP_AES_KUP_KEY && | 
|  | tfm_ctx->keylen != ZYNQMP_AES_KEY_SIZE) { | 
|  | need_fallback = 1; | 
|  | } | 
|  | if (req->assoclen != 0 || | 
|  | req->cryptlen < ZYNQMP_AES_MIN_INPUT_BLK_SIZE) { | 
|  | need_fallback = 1; | 
|  | } | 
|  | if ((req->cryptlen % ZYNQMP_AES_WORD_LEN) != 0) | 
|  | need_fallback = 1; | 
|  |  | 
|  | if (rq_ctx->op == ZYNQMP_AES_DECRYPT && | 
|  | req->cryptlen <= ZYNQMP_AES_AUTH_SIZE) { | 
|  | need_fallback = 1; | 
|  | } | 
|  | return need_fallback; | 
|  | } | 
|  |  | 
|  | static int zynqmp_handle_aes_req(struct crypto_engine *engine, | 
|  | void *req) | 
|  | { | 
|  | struct aead_request *areq = | 
|  | container_of(req, struct aead_request, base); | 
|  | struct crypto_aead *aead = crypto_aead_reqtfm(req); | 
|  | struct zynqmp_aead_tfm_ctx *tfm_ctx = crypto_aead_ctx(aead); | 
|  | struct zynqmp_aead_req_ctx *rq_ctx = aead_request_ctx(areq); | 
|  | struct aead_request *subreq = aead_request_ctx(req); | 
|  | int need_fallback; | 
|  | int err; | 
|  |  | 
|  | need_fallback = zynqmp_fallback_check(tfm_ctx, areq); | 
|  |  | 
|  | if (need_fallback) { | 
|  | aead_request_set_tfm(subreq, tfm_ctx->fbk_cipher); | 
|  |  | 
|  | aead_request_set_callback(subreq, areq->base.flags, | 
|  | NULL, NULL); | 
|  | aead_request_set_crypt(subreq, areq->src, areq->dst, | 
|  | areq->cryptlen, areq->iv); | 
|  | aead_request_set_ad(subreq, areq->assoclen); | 
|  | if (rq_ctx->op == ZYNQMP_AES_ENCRYPT) | 
|  | err = crypto_aead_encrypt(subreq); | 
|  | else | 
|  | err = crypto_aead_decrypt(subreq); | 
|  | } else { | 
|  | err = zynqmp_aes_aead_cipher(areq); | 
|  | } | 
|  |  | 
|  | crypto_finalize_aead_request(engine, areq, err); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int zynqmp_aes_aead_setkey(struct crypto_aead *aead, const u8 *key, | 
|  | unsigned int keylen) | 
|  | { | 
|  | struct crypto_tfm *tfm = crypto_aead_tfm(aead); | 
|  | struct zynqmp_aead_tfm_ctx *tfm_ctx = | 
|  | (struct zynqmp_aead_tfm_ctx *)crypto_tfm_ctx(tfm); | 
|  | unsigned char keysrc; | 
|  |  | 
|  | if (keylen == ZYNQMP_KEY_SRC_SEL_KEY_LEN) { | 
|  | keysrc = *key; | 
|  | if (keysrc == ZYNQMP_AES_KUP_KEY || | 
|  | keysrc == ZYNQMP_AES_DEV_KEY || | 
|  | keysrc == ZYNQMP_AES_PUF_KEY) { | 
|  | tfm_ctx->keysrc = (enum zynqmp_aead_keysrc)keysrc; | 
|  | } else { | 
|  | tfm_ctx->keylen = keylen; | 
|  | } | 
|  | } else { | 
|  | tfm_ctx->keylen = keylen; | 
|  | if (keylen == ZYNQMP_AES_KEY_SIZE) { | 
|  | tfm_ctx->keysrc = ZYNQMP_AES_KUP_KEY; | 
|  | memcpy(tfm_ctx->key, key, keylen); | 
|  | } | 
|  | } | 
|  |  | 
|  | tfm_ctx->fbk_cipher->base.crt_flags &= ~CRYPTO_TFM_REQ_MASK; | 
|  | tfm_ctx->fbk_cipher->base.crt_flags |= (aead->base.crt_flags & | 
|  | CRYPTO_TFM_REQ_MASK); | 
|  |  | 
|  | return crypto_aead_setkey(tfm_ctx->fbk_cipher, key, keylen); | 
|  | } | 
|  |  | 
|  | static int zynqmp_aes_aead_setauthsize(struct crypto_aead *aead, | 
|  | unsigned int authsize) | 
|  | { | 
|  | struct crypto_tfm *tfm = crypto_aead_tfm(aead); | 
|  | struct zynqmp_aead_tfm_ctx *tfm_ctx = | 
|  | (struct zynqmp_aead_tfm_ctx *)crypto_tfm_ctx(tfm); | 
|  |  | 
|  | tfm_ctx->authsize = authsize; | 
|  | return crypto_aead_setauthsize(tfm_ctx->fbk_cipher, authsize); | 
|  | } | 
|  |  | 
|  | static int zynqmp_aes_aead_encrypt(struct aead_request *req) | 
|  | { | 
|  | struct zynqmp_aead_drv_ctx *drv_ctx; | 
|  | struct crypto_aead *aead = crypto_aead_reqtfm(req); | 
|  | struct aead_alg *alg = crypto_aead_alg(aead); | 
|  | struct zynqmp_aead_req_ctx *rq_ctx = aead_request_ctx(req); | 
|  |  | 
|  | rq_ctx->op = ZYNQMP_AES_ENCRYPT; | 
|  | drv_ctx = container_of(alg, struct zynqmp_aead_drv_ctx, alg.aead); | 
|  |  | 
|  | return crypto_transfer_aead_request_to_engine(drv_ctx->engine, req); | 
|  | } | 
|  |  | 
|  | static int zynqmp_aes_aead_decrypt(struct aead_request *req) | 
|  | { | 
|  | struct zynqmp_aead_drv_ctx *drv_ctx; | 
|  | struct crypto_aead *aead = crypto_aead_reqtfm(req); | 
|  | struct aead_alg *alg = crypto_aead_alg(aead); | 
|  | struct zynqmp_aead_req_ctx *rq_ctx = aead_request_ctx(req); | 
|  |  | 
|  | rq_ctx->op = ZYNQMP_AES_DECRYPT; | 
|  | drv_ctx = container_of(alg, struct zynqmp_aead_drv_ctx, alg.aead); | 
|  |  | 
|  | return crypto_transfer_aead_request_to_engine(drv_ctx->engine, req); | 
|  | } | 
|  |  | 
|  | static int zynqmp_aes_aead_init(struct crypto_aead *aead) | 
|  | { | 
|  | struct crypto_tfm *tfm = crypto_aead_tfm(aead); | 
|  | struct zynqmp_aead_tfm_ctx *tfm_ctx = | 
|  | (struct zynqmp_aead_tfm_ctx *)crypto_tfm_ctx(tfm); | 
|  | struct zynqmp_aead_drv_ctx *drv_ctx; | 
|  | struct aead_alg *alg = crypto_aead_alg(aead); | 
|  |  | 
|  | drv_ctx = container_of(alg, struct zynqmp_aead_drv_ctx, alg.aead); | 
|  | tfm_ctx->dev = drv_ctx->dev; | 
|  |  | 
|  | tfm_ctx->engine_ctx.op.do_one_request = zynqmp_handle_aes_req; | 
|  | tfm_ctx->engine_ctx.op.prepare_request = NULL; | 
|  | tfm_ctx->engine_ctx.op.unprepare_request = NULL; | 
|  |  | 
|  | tfm_ctx->fbk_cipher = crypto_alloc_aead(drv_ctx->alg.aead.base.cra_name, | 
|  | 0, | 
|  | CRYPTO_ALG_NEED_FALLBACK); | 
|  |  | 
|  | if (IS_ERR(tfm_ctx->fbk_cipher)) { | 
|  | pr_err("%s() Error: failed to allocate fallback for %s\n", | 
|  | __func__, drv_ctx->alg.aead.base.cra_name); | 
|  | return PTR_ERR(tfm_ctx->fbk_cipher); | 
|  | } | 
|  |  | 
|  | crypto_aead_set_reqsize(aead, | 
|  | max(sizeof(struct zynqmp_aead_req_ctx), | 
|  | sizeof(struct aead_request) + | 
|  | crypto_aead_reqsize(tfm_ctx->fbk_cipher))); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void zynqmp_aes_aead_exit(struct crypto_aead *aead) | 
|  | { | 
|  | struct crypto_tfm *tfm = crypto_aead_tfm(aead); | 
|  | struct zynqmp_aead_tfm_ctx *tfm_ctx = | 
|  | (struct zynqmp_aead_tfm_ctx *)crypto_tfm_ctx(tfm); | 
|  |  | 
|  | if (tfm_ctx->fbk_cipher) { | 
|  | crypto_free_aead(tfm_ctx->fbk_cipher); | 
|  | tfm_ctx->fbk_cipher = NULL; | 
|  | } | 
|  | memzero_explicit(tfm_ctx, sizeof(struct zynqmp_aead_tfm_ctx)); | 
|  | } | 
|  |  | 
|  | static struct zynqmp_aead_drv_ctx aes_drv_ctx = { | 
|  | .alg.aead = { | 
|  | .setkey		= zynqmp_aes_aead_setkey, | 
|  | .setauthsize	= zynqmp_aes_aead_setauthsize, | 
|  | .encrypt	= zynqmp_aes_aead_encrypt, | 
|  | .decrypt	= zynqmp_aes_aead_decrypt, | 
|  | .init		= zynqmp_aes_aead_init, | 
|  | .exit		= zynqmp_aes_aead_exit, | 
|  | .ivsize		= GCM_AES_IV_SIZE, | 
|  | .maxauthsize	= ZYNQMP_AES_AUTH_SIZE, | 
|  | .base = { | 
|  | .cra_name		= "gcm(aes)", | 
|  | .cra_driver_name	= "xilinx-zynqmp-aes-gcm", | 
|  | .cra_priority		= 200, | 
|  | .cra_flags		= CRYPTO_ALG_TYPE_AEAD | | 
|  | CRYPTO_ALG_ASYNC | | 
|  | CRYPTO_ALG_ALLOCATES_MEMORY | | 
|  | CRYPTO_ALG_KERN_DRIVER_ONLY | | 
|  | CRYPTO_ALG_NEED_FALLBACK, | 
|  | .cra_blocksize		= ZYNQMP_AES_BLK_SIZE, | 
|  | .cra_ctxsize		= sizeof(struct zynqmp_aead_tfm_ctx), | 
|  | .cra_module		= THIS_MODULE, | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | static int zynqmp_aes_aead_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct device *dev = &pdev->dev; | 
|  | int err; | 
|  |  | 
|  | /* ZynqMP AES driver supports only one instance */ | 
|  | if (!aes_drv_ctx.dev) | 
|  | aes_drv_ctx.dev = dev; | 
|  | else | 
|  | return -ENODEV; | 
|  |  | 
|  | err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(ZYNQMP_DMA_BIT_MASK)); | 
|  | if (err < 0) { | 
|  | dev_err(dev, "No usable DMA configuration\n"); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | aes_drv_ctx.engine = crypto_engine_alloc_init(dev, 1); | 
|  | if (!aes_drv_ctx.engine) { | 
|  | dev_err(dev, "Cannot alloc AES engine\n"); | 
|  | err = -ENOMEM; | 
|  | goto err_engine; | 
|  | } | 
|  |  | 
|  | err = crypto_engine_start(aes_drv_ctx.engine); | 
|  | if (err) { | 
|  | dev_err(dev, "Cannot start AES engine\n"); | 
|  | goto err_engine; | 
|  | } | 
|  |  | 
|  | err = crypto_register_aead(&aes_drv_ctx.alg.aead); | 
|  | if (err < 0) { | 
|  | dev_err(dev, "Failed to register AEAD alg.\n"); | 
|  | goto err_aead; | 
|  | } | 
|  | return 0; | 
|  |  | 
|  | err_aead: | 
|  | crypto_unregister_aead(&aes_drv_ctx.alg.aead); | 
|  |  | 
|  | err_engine: | 
|  | if (aes_drv_ctx.engine) | 
|  | crypto_engine_exit(aes_drv_ctx.engine); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int zynqmp_aes_aead_remove(struct platform_device *pdev) | 
|  | { | 
|  | crypto_engine_exit(aes_drv_ctx.engine); | 
|  | crypto_unregister_aead(&aes_drv_ctx.alg.aead); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct of_device_id zynqmp_aes_dt_ids[] = { | 
|  | { .compatible = "xlnx,zynqmp-aes" }, | 
|  | { /* sentinel */ } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, zynqmp_aes_dt_ids); | 
|  |  | 
|  | static struct platform_driver zynqmp_aes_driver = { | 
|  | .probe	= zynqmp_aes_aead_probe, | 
|  | .remove = zynqmp_aes_aead_remove, | 
|  | .driver = { | 
|  | .name		= "zynqmp-aes", | 
|  | .of_match_table = zynqmp_aes_dt_ids, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | module_platform_driver(zynqmp_aes_driver); | 
|  | MODULE_LICENSE("GPL"); |