Commit f63601fd authored by Boris BREZILLON's avatar Boris BREZILLON Committed by Herbert Xu
Browse files

crypto: marvell/cesa - add a new driver for Marvell's CESA



The existing mv_cesa driver supports some features of the CESA IP but is
quite limited, and reworking it to support new features (like involving the
TDMA engine to offload the CPU) is almost impossible.
This driver has been rewritten from scratch to take those new features into
account.

This commit introduce the base infrastructure allowing us to add support
for DMA optimization.
It also includes support for one hash (SHA1) and one cipher (AES)
algorithm, and enable those features on the Armada 370 SoC.

Other algorithms and platforms will be added later on.
Signed-off-by: default avatarBoris Brezillon <boris.brezillon@free-electrons.com>
Signed-off-by: default avatarArnaud Ebalard <arno@natisbad.org>
Signed-off-by: default avatarHerbert Xu <herbert@gondor.apana.org.au>
parent 1fa2e9ae
......@@ -173,6 +173,21 @@ config CRYPTO_DEV_MV_CESA
Currently the driver supports AES in ECB and CBC mode without DMA.
config CRYPTO_DEV_MARVELL_CESA
tristate "New Marvell's Cryptographic Engine driver"
depends on (PLAT_ORION || ARCH_MVEBU || COMPILE_TEST) && HAS_DMA && HAS_IOMEM
select CRYPTO_AES
select CRYPTO_DES
select CRYPTO_BLKCIPHER
select CRYPTO_HASH
select SRAM
help
This driver allows you to utilize the Cryptographic Engines and
Security Accelerator (CESA) which can be found on the Armada 370.
This driver is aimed at replacing the mv_cesa driver. This will only
happen once it has received proper testing.
config CRYPTO_DEV_NIAGARA2
tristate "Niagara2 Stream Processing Unit driver"
select CRYPTO_DES
......
......@@ -9,6 +9,7 @@ obj-$(CONFIG_CRYPTO_DEV_HIFN_795X) += hifn_795x.o
obj-$(CONFIG_CRYPTO_DEV_IMGTEC_HASH) += img-hash.o
obj-$(CONFIG_CRYPTO_DEV_IXP4XX) += ixp4xx_crypto.o
obj-$(CONFIG_CRYPTO_DEV_MV_CESA) += mv_cesa.o
obj-$(CONFIG_CRYPTO_DEV_MARVELL_CESA) += marvell/
obj-$(CONFIG_CRYPTO_DEV_MXS_DCP) += mxs-dcp.o
obj-$(CONFIG_CRYPTO_DEV_NIAGARA2) += n2_crypto.o
n2_crypto-y := n2_core.o n2_asm.o
......
obj-$(CONFIG_CRYPTO_DEV_MARVELL_CESA) += marvell-cesa.o
marvell-cesa-objs := cesa.o cipher.o hash.o
/*
* Support for Marvell's Cryptographic Engine and Security Accelerator (CESA)
* that can be found on the following platform: Orion, Kirkwood, Armada. This
* driver supports the TDMA engine on platforms on which it is available.
*
* Author: Boris Brezillon <boris.brezillon@free-electrons.com>
* Author: Arnaud Ebalard <arno@natisbad.org>
*
* This work is based on an initial version written by
* Sebastian Andrzej Siewior < sebastian at breakpoint dot cc >
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*/
#include <linux/delay.h>
#include <linux/genalloc.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kthread.h>
#include <linux/mbus.h>
#include <linux/platform_device.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_irq.h>
#include "cesa.h"
struct mv_cesa_dev *cesa_dev;
static void mv_cesa_dequeue_req_unlocked(struct mv_cesa_engine *engine)
{
struct crypto_async_request *req, *backlog;
struct mv_cesa_ctx *ctx;
spin_lock_bh(&cesa_dev->lock);
backlog = crypto_get_backlog(&cesa_dev->queue);
req = crypto_dequeue_request(&cesa_dev->queue);
engine->req = req;
spin_unlock_bh(&cesa_dev->lock);
if (!req)
return;
if (backlog)
backlog->complete(backlog, -EINPROGRESS);
ctx = crypto_tfm_ctx(req->tfm);
ctx->ops->prepare(req, engine);
ctx->ops->step(req);
}
static irqreturn_t mv_cesa_int(int irq, void *priv)
{
struct mv_cesa_engine *engine = priv;
struct crypto_async_request *req;
struct mv_cesa_ctx *ctx;
u32 status, mask;
irqreturn_t ret = IRQ_NONE;
while (true) {
int res;
mask = mv_cesa_get_int_mask(engine);
status = readl(engine->regs + CESA_SA_INT_STATUS);
if (!(status & mask))
break;
/*
* TODO: avoid clearing the FPGA_INT_STATUS if this not
* relevant on some platforms.
*/
writel(~status, engine->regs + CESA_SA_FPGA_INT_STATUS);
writel(~status, engine->regs + CESA_SA_INT_STATUS);
ret = IRQ_HANDLED;
spin_lock_bh(&engine->lock);
req = engine->req;
spin_unlock_bh(&engine->lock);
if (req) {
ctx = crypto_tfm_ctx(req->tfm);
res = ctx->ops->process(req, status & mask);
if (res != -EINPROGRESS) {
spin_lock_bh(&engine->lock);
engine->req = NULL;
mv_cesa_dequeue_req_unlocked(engine);
spin_unlock_bh(&engine->lock);
ctx->ops->cleanup(req);
local_bh_disable();
req->complete(req, res);
local_bh_enable();
} else {
ctx->ops->step(req);
}
}
}
return ret;
}
int mv_cesa_queue_req(struct crypto_async_request *req)
{
int ret;
int i;
spin_lock_bh(&cesa_dev->lock);
ret = crypto_enqueue_request(&cesa_dev->queue, req);
spin_unlock_bh(&cesa_dev->lock);
if (ret != -EINPROGRESS)
return ret;
for (i = 0; i < cesa_dev->caps->nengines; i++) {
spin_lock_bh(&cesa_dev->engines[i].lock);
if (!cesa_dev->engines[i].req)
mv_cesa_dequeue_req_unlocked(&cesa_dev->engines[i]);
spin_unlock_bh(&cesa_dev->engines[i].lock);
}
return -EINPROGRESS;
}
static int mv_cesa_add_algs(struct mv_cesa_dev *cesa)
{
int ret;
int i, j;
for (i = 0; i < cesa->caps->ncipher_algs; i++) {
ret = crypto_register_alg(cesa->caps->cipher_algs[i]);
if (ret)
goto err_unregister_crypto;
}
for (i = 0; i < cesa->caps->nahash_algs; i++) {
ret = crypto_register_ahash(cesa->caps->ahash_algs[i]);
if (ret)
goto err_unregister_ahash;
}
return 0;
err_unregister_ahash:
for (j = 0; j < i; j++)
crypto_unregister_ahash(cesa->caps->ahash_algs[j]);
i = cesa->caps->ncipher_algs;
err_unregister_crypto:
for (j = 0; j < i; j++)
crypto_unregister_alg(cesa->caps->cipher_algs[j]);
return ret;
}
static void mv_cesa_remove_algs(struct mv_cesa_dev *cesa)
{
int i;
for (i = 0; i < cesa->caps->nahash_algs; i++)
crypto_unregister_ahash(cesa->caps->ahash_algs[i]);
for (i = 0; i < cesa->caps->ncipher_algs; i++)
crypto_unregister_alg(cesa->caps->cipher_algs[i]);
}
static struct crypto_alg *armada_370_cipher_algs[] = {
&mv_cesa_ecb_aes_alg,
&mv_cesa_cbc_aes_alg,
};
static struct ahash_alg *armada_370_ahash_algs[] = {
&mv_sha1_alg,
&mv_ahmac_sha1_alg,
};
static const struct mv_cesa_caps armada_370_caps = {
.nengines = 1,
.cipher_algs = armada_370_cipher_algs,
.ncipher_algs = ARRAY_SIZE(armada_370_cipher_algs),
.ahash_algs = armada_370_ahash_algs,
.nahash_algs = ARRAY_SIZE(armada_370_ahash_algs),
};
static const struct of_device_id mv_cesa_of_match_table[] = {
{ .compatible = "marvell,armada-370-crypto", .data = &armada_370_caps },
{}
};
MODULE_DEVICE_TABLE(of, mv_cesa_of_match_table);
static int mv_cesa_get_sram(struct platform_device *pdev, int idx)
{
struct mv_cesa_dev *cesa = platform_get_drvdata(pdev);
struct mv_cesa_engine *engine = &cesa->engines[idx];
const char *res_name = "sram";
struct resource *res;
engine->pool = of_get_named_gen_pool(cesa->dev->of_node,
"marvell,crypto-srams",
idx);
if (engine->pool) {
engine->sram = gen_pool_dma_alloc(engine->pool,
cesa->sram_size,
&engine->sram_dma);
if (engine->sram)
return 0;
engine->pool = NULL;
return -ENOMEM;
}
if (cesa->caps->nengines > 1) {
if (!idx)
res_name = "sram0";
else
res_name = "sram1";
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
res_name);
if (!res || resource_size(res) < cesa->sram_size)
return -EINVAL;
engine->sram = devm_ioremap_resource(cesa->dev, res);
if (IS_ERR(engine->sram))
return PTR_ERR(engine->sram);
engine->sram_dma = phys_to_dma(cesa->dev,
(phys_addr_t)res->start);
return 0;
}
static void mv_cesa_put_sram(struct platform_device *pdev, int idx)
{
struct mv_cesa_dev *cesa = platform_get_drvdata(pdev);
struct mv_cesa_engine *engine = &cesa->engines[idx];
if (!engine->pool)
return;
gen_pool_free(engine->pool, (unsigned long)engine->sram,
cesa->sram_size);
}
static int mv_cesa_probe(struct platform_device *pdev)
{
const struct mv_cesa_caps *caps = NULL;
const struct mbus_dram_target_info *dram;
const struct of_device_id *match;
struct device *dev = &pdev->dev;
struct mv_cesa_dev *cesa;
struct mv_cesa_engine *engines;
struct resource *res;
int irq, ret, i;
u32 sram_size;
if (cesa_dev) {
dev_err(&pdev->dev, "Only one CESA device authorized\n");
return -EEXIST;
}
if (!dev->of_node)
return -ENOTSUPP;
match = of_match_node(mv_cesa_of_match_table, dev->of_node);
if (!match || !match->data)
return -ENOTSUPP;
caps = match->data;
cesa = devm_kzalloc(dev, sizeof(*cesa), GFP_KERNEL);
if (!cesa)
return -ENOMEM;
cesa->caps = caps;
cesa->dev = dev;
sram_size = CESA_SA_DEFAULT_SRAM_SIZE;
of_property_read_u32(cesa->dev->of_node, "marvell,crypto-sram-size",
&sram_size);
if (sram_size < CESA_SA_MIN_SRAM_SIZE)
sram_size = CESA_SA_MIN_SRAM_SIZE;
cesa->sram_size = sram_size;
cesa->engines = devm_kzalloc(dev, caps->nengines * sizeof(*engines),
GFP_KERNEL);
if (!cesa->engines)
return -ENOMEM;
spin_lock_init(&cesa->lock);
crypto_init_queue(&cesa->queue, 50);
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
cesa->regs = devm_ioremap_resource(dev, res);
if (IS_ERR(cesa->regs))
return -ENOMEM;
dram = mv_mbus_dram_info_nooverlap();
platform_set_drvdata(pdev, cesa);
for (i = 0; i < caps->nengines; i++) {
struct mv_cesa_engine *engine = &cesa->engines[i];
char res_name[7];
engine->id = i;
spin_lock_init(&engine->lock);
ret = mv_cesa_get_sram(pdev, i);
if (ret)
goto err_cleanup;
irq = platform_get_irq(pdev, i);
if (irq < 0) {
ret = irq;
goto err_cleanup;
}
/*
* Not all platforms can gate the CESA clocks: do not complain
* if the clock does not exist.
*/
snprintf(res_name, sizeof(res_name), "cesa%d", i);
engine->clk = devm_clk_get(dev, res_name);
if (IS_ERR(engine->clk)) {
engine->clk = devm_clk_get(dev, NULL);
if (IS_ERR(engine->clk))
engine->clk = NULL;
}
snprintf(res_name, sizeof(res_name), "cesaz%d", i);
engine->zclk = devm_clk_get(dev, res_name);
if (IS_ERR(engine->zclk))
engine->zclk = NULL;
ret = clk_prepare_enable(engine->clk);
if (ret)
goto err_cleanup;
ret = clk_prepare_enable(engine->zclk);
if (ret)
goto err_cleanup;
engine->regs = cesa->regs + CESA_ENGINE_OFF(i);
writel(0, cesa->engines[i].regs + CESA_SA_INT_STATUS);
writel(CESA_SA_CFG_STOP_DIG_ERR,
cesa->engines[i].regs + CESA_SA_CFG);
writel(engine->sram_dma & CESA_SA_SRAM_MSK,
cesa->engines[i].regs + CESA_SA_DESC_P0);
ret = devm_request_threaded_irq(dev, irq, NULL, mv_cesa_int,
IRQF_ONESHOT,
dev_name(&pdev->dev),
&cesa->engines[i]);
if (ret)
goto err_cleanup;
}
cesa_dev = cesa;
ret = mv_cesa_add_algs(cesa);
if (ret) {
cesa_dev = NULL;
goto err_cleanup;
}
dev_info(dev, "CESA device successfully registered\n");
return 0;
err_cleanup:
for (i = 0; i < caps->nengines; i++) {
clk_disable_unprepare(cesa->engines[i].zclk);
clk_disable_unprepare(cesa->engines[i].clk);
mv_cesa_put_sram(pdev, i);
}
return ret;
}
static int mv_cesa_remove(struct platform_device *pdev)
{
struct mv_cesa_dev *cesa = platform_get_drvdata(pdev);
int i;
mv_cesa_remove_algs(cesa);
for (i = 0; i < cesa->caps->nengines; i++) {
clk_disable_unprepare(cesa->engines[i].zclk);
clk_disable_unprepare(cesa->engines[i].clk);
mv_cesa_put_sram(pdev, i);
}
return 0;
}
static struct platform_driver marvell_cesa = {
.probe = mv_cesa_probe,
.remove = mv_cesa_remove,
.driver = {
.owner = THIS_MODULE,
.name = "marvell-cesa",
.of_match_table = mv_cesa_of_match_table,
},
};
module_platform_driver(marvell_cesa);
MODULE_ALIAS("platform:mv_crypto");
MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
MODULE_AUTHOR("Arnaud Ebalard <arno@natisbad.org>");
MODULE_DESCRIPTION("Support for Marvell's cryptographic engine");
MODULE_LICENSE("GPL v2");
#ifndef __MARVELL_CESA_H__
#define __MARVELL_CESA_H__
#include <crypto/algapi.h>
#include <crypto/hash.h>
#include <crypto/internal/hash.h>
#include <linux/crypto.h>
#define CESA_ENGINE_OFF(i) (((i) * 0x2000))
#define CESA_TDMA_BYTE_CNT 0x800
#define CESA_TDMA_SRC_ADDR 0x810
#define CESA_TDMA_DST_ADDR 0x820
#define CESA_TDMA_NEXT_ADDR 0x830
#define CESA_TDMA_CONTROL 0x840
#define CESA_TDMA_DST_BURST GENMASK(2, 0)
#define CESA_TDMA_DST_BURST_32B 3
#define CESA_TDMA_DST_BURST_128B 4
#define CESA_TDMA_OUT_RD_EN BIT(4)
#define CESA_TDMA_SRC_BURST GENMASK(8, 6)
#define CESA_TDMA_SRC_BURST_32B (3 << 6)
#define CESA_TDMA_SRC_BURST_128B (4 << 6)
#define CESA_TDMA_CHAIN BIT(9)
#define CESA_TDMA_BYTE_SWAP BIT(11)
#define CESA_TDMA_NO_BYTE_SWAP BIT(11)
#define CESA_TDMA_EN BIT(12)
#define CESA_TDMA_FETCH_ND BIT(13)
#define CESA_TDMA_ACT BIT(14)
#define CESA_TDMA_CUR 0x870
#define CESA_TDMA_ERROR_CAUSE 0x8c8
#define CESA_TDMA_ERROR_MSK 0x8cc
#define CESA_TDMA_WINDOW_BASE(x) (((x) * 0x8) + 0xa00)
#define CESA_TDMA_WINDOW_CTRL(x) (((x) * 0x8) + 0xa04)
#define CESA_IVDIG(x) (0xdd00 + ((x) * 4) + \
(((x) < 5) ? 0 : 0x14))
#define CESA_SA_CMD 0xde00
#define CESA_SA_CMD_EN_CESA_SA_ACCL0 BIT(0)
#define CESA_SA_CMD_EN_CESA_SA_ACCL1 BIT(1)
#define CESA_SA_CMD_DISABLE_SEC BIT(2)
#define CESA_SA_DESC_P0 0xde04
#define CESA_SA_DESC_P1 0xde14
#define CESA_SA_CFG 0xde08
#define CESA_SA_CFG_STOP_DIG_ERR GENMASK(1, 0)
#define CESA_SA_CFG_DIG_ERR_CONT 0
#define CESA_SA_CFG_DIG_ERR_SKIP 1
#define CESA_SA_CFG_DIG_ERR_STOP 3
#define CESA_SA_CFG_CH0_W_IDMA BIT(7)
#define CESA_SA_CFG_CH1_W_IDMA BIT(8)
#define CESA_SA_CFG_ACT_CH0_IDMA BIT(9)
#define CESA_SA_CFG_ACT_CH1_IDMA BIT(10)
#define CESA_SA_CFG_MULTI_PKT BIT(11)
#define CESA_SA_CFG_PARA_DIS BIT(13)
#define CESA_SA_ACCEL_STATUS 0xde0c
#define CESA_SA_ST_ACT_0 BIT(0)
#define CESA_SA_ST_ACT_1 BIT(1)
/*
* CESA_SA_FPGA_INT_STATUS looks like a FPGA leftover and is documented only
* in Errata 4.12. It looks like that it was part of an IRQ-controller in FPGA
* and someone forgot to remove it while switching to the core and moving to
* CESA_SA_INT_STATUS.
*/
#define CESA_SA_FPGA_INT_STATUS 0xdd68
#define CESA_SA_INT_STATUS 0xde20
#define CESA_SA_INT_AUTH_DONE BIT(0)
#define CESA_SA_INT_DES_E_DONE BIT(1)
#define CESA_SA_INT_AES_E_DONE BIT(2)
#define CESA_SA_INT_AES_D_DONE BIT(3)
#define CESA_SA_INT_ENC_DONE BIT(4)
#define CESA_SA_INT_ACCEL0_DONE BIT(5)
#define CESA_SA_INT_ACCEL1_DONE BIT(6)
#define CESA_SA_INT_ACC0_IDMA_DONE BIT(7)
#define CESA_SA_INT_ACC1_IDMA_DONE BIT(8)
#define CESA_SA_INT_IDMA_DONE BIT(9)
#define CESA_SA_INT_IDMA_OWN_ERR BIT(10)
#define CESA_SA_INT_MSK 0xde24
#define CESA_SA_DESC_CFG_OP_MAC_ONLY 0
#define CESA_SA_DESC_CFG_OP_CRYPT_ONLY 1
#define CESA_SA_DESC_CFG_OP_MAC_CRYPT 2
#define CESA_SA_DESC_CFG_OP_CRYPT_MAC 3
#define CESA_SA_DESC_CFG_OP_MSK GENMASK(1, 0)
#define CESA_SA_DESC_CFG_MACM_SHA256 (1 << 4)
#define CESA_SA_DESC_CFG_MACM_HMAC_SHA256 (3 << 4)
#define CESA_SA_DESC_CFG_MACM_MD5 (4 << 4)
#define CESA_SA_DESC_CFG_MACM_SHA1 (5 << 4)
#define CESA_SA_DESC_CFG_MACM_HMAC_MD5 (6 << 4)
#define CESA_SA_DESC_CFG_MACM_HMAC_SHA1 (7 << 4)
#define CESA_SA_DESC_CFG_MACM_MSK GENMASK(6, 4)
#define CESA_SA_DESC_CFG_CRYPTM_DES (1 << 8)
#define CESA_SA_DESC_CFG_CRYPTM_3DES (2 << 8)
#define CESA_SA_DESC_CFG_CRYPTM_AES (3 << 8)
#define CESA_SA_DESC_CFG_CRYPTM_MSK GENMASK(9, 8)
#define CESA_SA_DESC_CFG_DIR_ENC (0 << 12)
#define CESA_SA_DESC_CFG_DIR_DEC (1 << 12)
#define CESA_SA_DESC_CFG_CRYPTCM_ECB (0 << 16)
#define CESA_SA_DESC_CFG_CRYPTCM_CBC (1 << 16)
#define CESA_SA_DESC_CFG_CRYPTCM_MSK BIT(16)
#define CESA_SA_DESC_CFG_3DES_EEE (0 << 20)
#define CESA_SA_DESC_CFG_3DES_EDE (1 << 20)
#define CESA_SA_DESC_CFG_AES_LEN_128 (0 << 24)
#define CESA_SA_DESC_CFG_AES_LEN_192 (1 << 24)
#define CESA_SA_DESC_CFG_AES_LEN_256 (2 << 24)
#define CESA_SA_DESC_CFG_AES_LEN_MSK GENMASK(25, 24)
#define CESA_SA_DESC_CFG_NOT_FRAG (0 << 30)
#define CESA_SA_DESC_CFG_FIRST_FRAG (1 << 30)
#define CESA_SA_DESC_CFG_LAST_FRAG (2 << 30)
#define CESA_SA_DESC_CFG_MID_FRAG (3 << 30)
#define CESA_SA_DESC_CFG_FRAG_MSK GENMASK(31, 30)
/*
* /-----------\ 0
* | ACCEL CFG | 4 * 8
* |-----------| 0x20
* | CRYPT KEY | 8 * 4
* |-----------| 0x40
* | IV IN | 4 * 4
* |-----------| 0x40 (inplace)
* | IV BUF | 4 * 4
* |-----------| 0x80
* | DATA IN | 16 * x (max ->max_req_size)
* |-----------| 0x80 (inplace operation)
* | DATA OUT | 16 * x (max ->max_req_size)
* \-----------/ SRAM size
*/
/*
* Hashing memory map:
* /-----------\ 0
* | ACCEL CFG | 4 * 8
* |-----------| 0x20
* | Inner IV | 8 * 4
* |-----------| 0x40
* | Outer IV | 8 * 4
* |-----------| 0x60
* | Output BUF| 8 * 4
* |-----------| 0x80
* | DATA IN | 64 * x (max ->max_req_size)
* \-----------/ SRAM size
*/
#define CESA_SA_CFG_SRAM_OFFSET 0x00
#define CESA_SA_DATA_SRAM_OFFSET 0x80
#define CESA_SA_CRYPT_KEY_SRAM_OFFSET 0x20
#define CESA_SA_CRYPT_IV_SRAM_OFFSET 0x40
#define CESA_SA_MAC_IIV_SRAM_OFFSET 0x20
#define CESA_SA_MAC_OIV_SRAM_OFFSET 0x40
#define CESA_SA_MAC_DIG_SRAM_OFFSET 0x60
#define CESA_SA_DESC_CRYPT_DATA(offset) \
cpu_to_le32((CESA_SA_DATA_SRAM_OFFSET + (offset)) | \
((CESA_SA_DATA_SRAM_OFFSET + (offset)) << 16))