[PATCH 15/16] crypto: eip28: Add support for SafeXcel EIP-28 Public Key Accelerator

Miquel Raynal (Schneider Electric) posted 16 patches 5 days, 18 hours ago
[PATCH 15/16] crypto: eip28: Add support for SafeXcel EIP-28 Public Key Accelerator
Posted by Miquel Raynal (Schneider Electric) 5 days, 18 hours ago
This engine provides offload for:
- RSA (RSA-PKCS1, FIPS 186-3)
- ECDSA (FIPS 186-3)
- ECDH (SP 800-56B)

The driver is stable and working, but currently only implements the RSA
algorithm. It successfully passes RSA selftests.

Signed-off-by: Miquel Raynal (Schneider Electric) <miquel.raynal@bootlin.com>
---
We are working on ECDSA and ECDH support, they will be added later.

We are also clarifying the terms and conditions to publish the firmware
in a repository like linux-firmwares, if ever possible.
---
 drivers/crypto/Kconfig                |  10 +
 drivers/crypto/inside-secure/Makefile |   1 +
 drivers/crypto/inside-secure/eip28.c  | 760 ++++++++++++++++++++++++++++++++++
 3 files changed, 771 insertions(+)

diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig
index b3a6bbf0aacf..734649a223f7 100644
--- a/drivers/crypto/Kconfig
+++ b/drivers/crypto/Kconfig
@@ -877,6 +877,16 @@ config CRYPTO_DEV_SAFEXCEL
 	  SHA384 and SHA512 hash algorithms for both basic hash and HMAC.
 	  Additionally, it accelerates combined AES-CBC/HMAC-SHA AEAD operations.
 
+config CRYPTO_DEV_EIP28
+	tristate "Support Inside Secure's SafeXcel EIP28 PKA"
+	depends on SAFEXCEL_EIP201_AIC
+	select CRYPTO_AKCIPHER
+	help
+	  EIP28 is a public key accelerator. It provides offload for:
+	  - RSA (RSA-PKCS1, FIPS 186-3)
+	  - ECDSA (FIPS 186-3)
+	  - ECDH (SP 800-56B)
+
 source "drivers/crypto/inside-secure/eip93/Kconfig"
 source "drivers/crypto/ti/Kconfig"
 
diff --git a/drivers/crypto/inside-secure/Makefile b/drivers/crypto/inside-secure/Makefile
index 30d13fd5d58e..8a6f51dcec58 100644
--- a/drivers/crypto/inside-secure/Makefile
+++ b/drivers/crypto/inside-secure/Makefile
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-$(CONFIG_CRYPTO_DEV_SAFEXCEL) += crypto_safexcel.o
 crypto_safexcel-objs := safexcel.o safexcel_ring.o safexcel_cipher.o safexcel_hash.o
+obj-$(CONFIG_CRYPTO_DEV_EIP28) += eip28.o
 obj-y += eip93/
diff --git a/drivers/crypto/inside-secure/eip28.c b/drivers/crypto/inside-secure/eip28.c
new file mode 100644
index 000000000000..495a8cf6dfc0
--- /dev/null
+++ b/drivers/crypto/inside-secure/eip28.c
@@ -0,0 +1,760 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019-2026 Schneider Electric
+ */
+
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/scatterlist.h>
+#include <linux/of.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/firmware.h>
+#include <crypto/internal/akcipher.h>
+#include <crypto/internal/rsa.h>
+
+#define EIP28_PKA_APTR 0x0
+#define EIP28_PKA_BPTR 0x04
+#define EIP28_PKA_CPTR 0x08
+#define EIP28_PKA_DPTR 0x0c
+#define EIP28_PKA_ALENGTH 0x10
+#define EIP28_PKA_BLENGTH 0x14
+#define   EIP28_PKA_VECTOR_MAXLEN 512
+#define EIP28_PKA_BITSHIFT 0x18
+#define EIP28_PKA_FUNCTION 0x1c
+#define   EIP28_FUNCTION_MULTIPLY 0
+#define   EIP28_FUNCTION_SEQ_MOD_EXP_CRT (1 << 12)
+#define   EIP28_FUNCTION_SEQ_MOD_EXP_ACT4 (2 << 12)
+#define   EIP28_FUNCTION_SEQ_ECC_ADD_AFFINE (3 << 12)
+#define   EIP28_FUNCTION_SEQ_MOD_EXP_ACT2 (4 << 12)
+#define   EIP28_FUNCTION_SEQ_ECC_MUL_AFFINE (5 << 12)
+#define   EIP28_FUNCTION_SEQ_MOD_EXP_VAR (6 << 12)
+#define   EIP28_FUNCTION_SEQ_MOD_INV (7 << 12)
+#define   EIP28_FUNCTION_RUN BIT(15)
+#define   EIP28_FUNCTION_SEQ_ECC_ADD_PROJECTIVE (16 << 12)
+#define   EIP28_FUNCTION_SEQ_ECC_MUL_PROJECTIVE (17 << 12)
+
+#define EIP28_PKA_SEQ_CTRL 0xc8
+#define   EIP28_CTRL_RESET BIT(31)
+#define   EIP28_CTRL_STATUS(reg) FIELD_GET(GENMASK(15, 8), (reg))
+#define   EIP28_CTRL_SEQ_IDLE 1
+
+#define EIP28_PKA_SW_REV 0xf8
+#define EIP28_PKA_REVISION 0xfc
+#define   EIP28_REV_MAJOR(reg) FIELD_GET(GENMASK(27, 24), (reg))
+#define   EIP28_REV_MINOR(reg) FIELD_GET(GENMASK(23, 20), (reg))
+#define   EIP28_REV_PATCH_LVL(reg) FIELD_GET(GENMASK(16, 16), (reg))
+#define   EIP28_REV_CAPABILITIES(reg) FIELD_GET(GENMASK(31, 28), (reg))
+
+#define EIP28_PKA_PROGRAM_RAM 0x2000
+#define EIP28_PKA_PROGRAM_RAM_SZ SZ_8K
+
+#define EIP28_PKA_NR_ODD_POW 2 /* 0 < bit_shift <= 16 */
+#define EIP28_FW_NAME "ram_code_eip28.bin"
+
+enum eip28_operation {
+	MOD_EXP_VAR,
+	MOD_EXP_ACT2,
+	MOD_EXP_ACT4,
+	MOD_EXP_CRT,
+	ECC_ADD_AFFINE,
+	ECC_ADD_PROJECTIVE,
+	ECC_MUL_AFFINE,
+	ECC_MUL_PROJECTIVE,
+	MULTIPLY,
+	MOD_INV
+};
+
+static struct eip28 *main_dev;
+
+struct eip28 {
+	void __iomem *regs;
+	struct device *dev;
+	struct clk *clk;
+	struct completion completion;
+	/* Serialize algorithms */
+	struct mutex lock;
+};
+
+struct eip28_rsa_ctx {
+	struct eip28 *priv;
+	u8 *n;
+	u8 *e;
+	u8 *d;
+	size_t n_sz;
+	size_t e_sz;
+	size_t d_sz;
+	unsigned char *message;
+};
+
+static void eip28_pkaram_write_word(struct eip28 *priv, u32 value, u32 word_off)
+{
+	writel(value, priv->regs + EIP28_PKA_PROGRAM_RAM + (word_off * 4));
+}
+
+static u32 eip28_pkaram_read_word(struct eip28 *priv, u32 word_off)
+{
+	return readl(priv->regs + EIP28_PKA_PROGRAM_RAM + (word_off * 4));
+}
+
+static void eip28_pkaram_write_vector(struct eip28 *priv, u32 *word_off,
+				      const u8 *buf, u32 bytes_count)
+{
+	u32 off;
+	int i;
+
+	off = *word_off + (bytes_count / 4) - 1;
+	for (i = 0; i < bytes_count; i += 4, off -= 1)
+		eip28_pkaram_write_word(priv, cpu_to_be32(*(u32 *)(buf + i)), off);
+
+	*word_off = round_up(*word_off * 4 + bytes_count + 1, 8) / 4;
+}
+
+static void eip28_pkaram_read_vector(struct eip28 *priv, u32 start_word,
+				     u8 *buf, u32 bytes_count)
+{
+	u32 off, *ptr;
+	int i;
+
+	memset(buf, 0, bytes_count);
+
+	off = start_word + (bytes_count / 4) - 1;
+	for (i = 0; i < bytes_count; i += 4, off -= 1) {
+		ptr = (u32 *)&buf[i];
+		*ptr = be32_to_cpu(eip28_pkaram_read_word(priv, off));
+	}
+}
+
+static size_t eip28_scratchpad_size(enum eip28_operation op)
+{
+	switch (op) {
+	case MOD_EXP_ACT2:
+	case MOD_EXP_ACT4:
+	case MOD_EXP_VAR:
+		return 9;
+	case MOD_EXP_CRT:
+		return 11;
+	case ECC_ADD_AFFINE:
+	case ECC_ADD_PROJECTIVE:
+	case ECC_MUL_AFFINE:
+	case ECC_MUL_PROJECTIVE:
+		return 19;
+	case MULTIPLY:
+		return 6;
+	case MOD_INV:
+		return 12;
+	default:
+		return 0;
+	}
+}
+
+static size_t eip28_work_area_size(enum eip28_operation op, u32 mod_len)
+{
+	u32 L = 0;
+
+	switch (op) {
+	case MOD_EXP_ACT2:
+	case MOD_EXP_ACT4:
+	case MOD_EXP_VAR:
+		/* (#odd powers + 3) x (B_len + 2) */
+		return (EIP28_PKA_NR_ODD_POW + 3) * (mod_len + 2);
+	case MOD_EXP_CRT:
+		/* (#odd powers + 3) x (B_len + 2) + (B_len + 2 - (B_len mod 2)) */
+		L = mod_len + 2 - (mod_len & 1);
+		return (EIP28_PKA_NR_ODD_POW + 3) * (mod_len + 2) + L;
+	case ECC_ADD_PROJECTIVE:
+	case ECC_ADD_AFFINE:
+	case ECC_MUL_PROJECTIVE:
+	case ECC_MUL_AFFINE:
+		/* 20 x (B_len + 2 + (B_len mod 2)) */
+		L = mod_len + 2 + (mod_len & 1);
+		return 20 * L;
+	case MULTIPLY:
+	case MOD_INV:
+	default:
+		return 0;
+	}
+}
+
+static size_t eip28_op_to_func(enum eip28_operation op)
+{
+	switch (op) {
+	case MOD_EXP_ACT2:
+		return EIP28_FUNCTION_SEQ_MOD_EXP_ACT2;
+	case MOD_EXP_ACT4:
+		return EIP28_FUNCTION_SEQ_MOD_EXP_ACT4;
+	case MOD_EXP_VAR:
+		return EIP28_FUNCTION_SEQ_MOD_EXP_VAR;
+	case MOD_EXP_CRT:
+		return EIP28_FUNCTION_SEQ_MOD_EXP_CRT;
+	case ECC_ADD_PROJECTIVE:
+		return EIP28_FUNCTION_SEQ_ECC_ADD_PROJECTIVE;
+	case ECC_ADD_AFFINE:
+		return EIP28_FUNCTION_SEQ_ECC_ADD_AFFINE;
+	case ECC_MUL_PROJECTIVE:
+		return EIP28_FUNCTION_SEQ_ECC_MUL_PROJECTIVE;
+	case ECC_MUL_AFFINE:
+		return EIP28_FUNCTION_SEQ_ECC_MUL_AFFINE;
+	case MULTIPLY:
+		return EIP28_FUNCTION_MULTIPLY;
+	case MOD_INV:
+		return EIP28_FUNCTION_SEQ_MOD_INV;
+	default:
+		WARN_ON_ONCE("EIP28 unsupported operation\n");
+		return 0;
+	}
+}
+
+static int eip28_vectors_overlap(u16 start_addr1, size_t len1, u16 start_addr2,	size_t len2)
+{
+	u32 end_addr1 = start_addr1 + len1 - 1;
+	u32 end_addr2 = start_addr2 + len2 - 1;
+
+	return !(end_addr1 < start_addr2 || end_addr2 < start_addr1);
+}
+
+static void eip28_trigger_operation(struct eip28 *priv, enum eip28_operation op)
+{
+	writel(eip28_op_to_func(op) | EIP28_FUNCTION_RUN,
+	       priv->regs + EIP28_PKA_FUNCTION);
+}
+
+static int eip28_run_mod_exp(struct eip28 *priv, u16 vector_a, u16 vector_b,
+			     u16 vector_c, u16 vector_d, size_t a_wlen, size_t bc_wlen)
+{
+	size_t scratchpad_off, result_wlen;
+	u32 val;
+
+	/* 0 < a_wlen <= MAX */
+	if (!a_wlen || a_wlen > EIP28_PKA_VECTOR_MAXLEN)
+		return -EINVAL;
+
+	/* 1 < b_wlen <= MAX */
+	if (bc_wlen < 2 ||  bc_wlen > EIP28_PKA_VECTOR_MAXLEN)
+		return -EINVAL;
+
+	scratchpad_off = (EIP28_PKA_PROGRAM_RAM_SZ / 4) - eip28_scratchpad_size(MOD_EXP_VAR);
+	result_wlen = eip28_work_area_size(MOD_EXP_VAR, bc_wlen);
+
+	/*
+	 * Make sure the area taken by each vector does not overlap with the
+	 * scratchpad, the other input vectors not the result vector.
+	 */
+	if ((vector_a + a_wlen > scratchpad_off) ||
+	    (vector_b + bc_wlen > scratchpad_off) ||
+	    (vector_c + bc_wlen > scratchpad_off) ||
+	    (vector_d + result_wlen > scratchpad_off))
+		return -EINVAL;
+
+	/* Ensure 64-bit alignment (offsets are words) */
+	if (!IS_ALIGNED(vector_a, 2) || !IS_ALIGNED(vector_b, 2) ||
+	    !IS_ALIGNED(vector_c, 2) || !IS_ALIGNED(vector_d, 2))
+		return -EINVAL;
+
+	/* Modulus must be odd */
+	val = eip28_pkaram_read_word(priv, vector_b);
+	if (!(val & 1))
+		return -EINVAL;
+
+	/*
+	 * Make sure there is no overlap between any of the input vectors and
+	 * the result vector. Only vector_c == vector_d is allowed.
+	 */
+	if (eip28_vectors_overlap(vector_a, a_wlen, vector_d, result_wlen) ||
+	    eip28_vectors_overlap(vector_b, bc_wlen, vector_d, result_wlen) ||
+	    (vector_c != vector_d &&
+	     eip28_vectors_overlap(vector_c, bc_wlen, vector_d, result_wlen)))
+		return -ERANGE;
+
+	writel(vector_a, priv->regs + EIP28_PKA_APTR);
+	writel(vector_b, priv->regs + EIP28_PKA_BPTR);
+	writel(vector_c, priv->regs + EIP28_PKA_CPTR);
+	writel(vector_d, priv->regs + EIP28_PKA_DPTR);
+	writel(a_wlen, priv->regs + EIP28_PKA_ALENGTH);
+	writel(bc_wlen, priv->regs + EIP28_PKA_BLENGTH);
+	writel(EIP28_PKA_NR_ODD_POW, priv->regs + EIP28_PKA_BITSHIFT);
+
+	eip28_trigger_operation(priv, MOD_EXP_VAR);
+
+	return 0;
+}
+
+/*
+ * EIP28 operates using vectors precisely located in PKARAM. A vector
+ * corresponds to a pointer of an element. In the RSA case:
+ *     vector A = exponent (e or d)
+ *     vector B = modulo (n)
+ *     vector C = message
+ *     vector D = result of the operations
+ *
+ *    PKARAM    Offset (pinternal_ram)
+ * +----------+ 0
+ * | vector A |
+ * +----------+ length(vector A)
+ * | vector B |
+ * +----------+ length(vector A) + length(vector B)
+ * | vector C |
+ * +----------+ length(vector A) + length(vector B) + length(vector C)
+ * | vector D |
+ * +----------+
+ */
+static int eip28_rsa_operation(struct akcipher_request *req, bool encrypt)
+{
+	struct crypto_akcipher *tfm = crypto_akcipher_reqtfm(req);
+	struct eip28_rsa_ctx *ctx = akcipher_tfm_ctx(tfm);
+	u16 vector_a, vector_b, vector_c, vector_d;
+	struct eip28 *priv = ctx->priv;
+	unsigned int pkaram_off = 0;
+	size_t a_wlen, bc_wlen;
+	u32 reg;
+	int ret;
+
+	/* Sanity check */
+	if (req->src_len > ctx->n_sz)
+		return -EMSGSIZE;
+
+	/* Check if no computation is running */
+	reg = readl(priv->regs + EIP28_PKA_FUNCTION);
+	if (reg & EIP28_FUNCTION_RUN)
+		return -EBUSY;
+
+	/* Check if PKARAM is accessible */
+	reg = readl(priv->regs + EIP28_PKA_SEQ_CTRL);
+	if (reg & EIP28_CTRL_RESET) {
+		dev_err(priv->dev, "PKARAM not accessible\n");
+		return -EBUSY;
+	}
+
+	/* Initializes vector A with either 'e' or 'd' and move the internal pointer */
+	vector_a = 0;
+	if (encrypt) {
+		eip28_pkaram_write_vector(priv, &pkaram_off, ctx->e, ctx->e_sz);
+		a_wlen = ctx->e_sz / 4;
+	} else {
+		eip28_pkaram_write_vector(priv, &pkaram_off, ctx->d, ctx->d_sz);
+		a_wlen = ctx->d_sz / 4;
+	}
+
+	/* Initializes Vector B with the modulo 'n' */
+	vector_b = pkaram_off;
+	eip28_pkaram_write_vector(priv, &pkaram_off, ctx->n, ctx->n_sz);
+	bc_wlen = ctx->n_sz / 4;
+
+	/* Initializes Vector C with the message */
+	vector_c = pkaram_off;
+	memset(ctx->message, 0, ctx->n_sz - req->src_len);
+	sg_copy_to_buffer(req->src, 1, &ctx->message[ctx->n_sz - req->src_len], req->src_len);
+	eip28_pkaram_write_vector(priv, &pkaram_off, ctx->message, ctx->n_sz);
+
+	/* Initialize vector D, output will overwrite the message */
+	vector_d = vector_c;
+
+	/* Trigger operation */
+	ret = eip28_run_mod_exp(priv, vector_a, vector_b, vector_c, vector_d,
+				a_wlen, bc_wlen);
+	if (ret) {
+		dev_err(priv->dev, "Modular exponentiation error : %d\n", ret);
+		return ret;
+	}
+
+	ret = wait_for_completion_timeout(&priv->completion, msecs_to_jiffies(2000));
+	if (!ret) {
+		reinit_completion(&priv->completion);
+		return -ETIMEDOUT;
+	}
+
+	/* Retrieve result */
+	reg = readl(priv->regs + EIP28_PKA_SEQ_CTRL);
+	if (EIP28_CTRL_STATUS(reg) != EIP28_CTRL_SEQ_IDLE)
+		return -EIO;
+
+	memset(ctx->message, 0, ctx->n_sz);
+	eip28_pkaram_read_vector(priv, vector_d, ctx->message, ctx->n_sz);
+	sg_copy_from_buffer(req->dst, 1, ctx->message, ctx->n_sz);
+
+	return 0;
+}
+
+static int eip28_rsa_save_modulo(struct eip28_rsa_ctx *ctx, struct rsa_key *key)
+{
+	unsigned int nskip;
+
+	/* Skip the first empty bytes in the buffer */
+	for (nskip = 0; nskip < key->n_sz; nskip++)
+		if (key->n[nskip])
+			break;
+
+	ctx->n_sz = key->n_sz - nskip;
+
+	ctx->n = kzalloc(round_up(ctx->n_sz, 4), GFP_KERNEL);
+	if (!ctx->n)
+		return -ENOMEM;
+
+	memcpy(ctx->n, key->n + nskip, ctx->n_sz);
+	if (!ctx->n)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int eip28_rsa_save_keypart(u8 **dst, size_t *dest_sz, const u8 *src, size_t src_sz,
+				  size_t modulo_size)
+{
+	unsigned int nskip, real_size_buf;
+
+	/* Skip the first empty bytes in the buffer */
+	for (nskip = 0; nskip < src_sz; nskip++)
+		if (src[nskip])
+			break;
+
+	real_size_buf = src_sz - nskip;
+	if (real_size_buf > modulo_size)
+		return -EINVAL;
+
+	/* Write data at the end of the buffer */
+	*dst = kzalloc(round_up(modulo_size, 4), GFP_KERNEL);
+	if (!*dst)
+		return -ENOMEM;
+
+	memcpy(*dst + (modulo_size - real_size_buf), src + nskip, real_size_buf);
+	*dest_sz = modulo_size;
+
+	return 0;
+}
+
+static void eip28_rsa_clean_ctx(struct eip28_rsa_ctx *ctx)
+{
+	struct eip28 *priv;
+
+	kfree(ctx->e);
+	kfree(ctx->n);
+	kfree(ctx->d);
+	kfree(ctx->message);
+
+	priv = ctx->priv;
+	memset(ctx, 0, sizeof(*ctx));
+	ctx->priv = priv;
+}
+
+static unsigned int eip28_rsa_max_size(struct crypto_akcipher *tfm)
+{
+	struct eip28_rsa_ctx *ctx = akcipher_tfm_ctx(tfm);
+
+	return ctx->n_sz;
+}
+
+static int eip28_rsa_encrypt(struct akcipher_request *req)
+{
+	struct crypto_akcipher *tfm = crypto_akcipher_reqtfm(req);
+	struct eip28_rsa_ctx *ctx = akcipher_tfm_ctx(tfm);
+	int ret;
+
+	mutex_lock(&ctx->priv->lock);
+	ret = eip28_rsa_operation(req, true);
+	mutex_unlock(&ctx->priv->lock);
+
+	return ret;
+}
+
+static int eip28_rsa_decrypt(struct akcipher_request *req)
+{
+	struct crypto_akcipher *tfm = crypto_akcipher_reqtfm(req);
+	struct eip28_rsa_ctx *ctx = akcipher_tfm_ctx(tfm);
+	int ret;
+
+	mutex_lock(&ctx->priv->lock);
+	ret = eip28_rsa_operation(req, false);
+	mutex_unlock(&ctx->priv->lock);
+
+	return ret;
+}
+
+static int eip28_rsa_check_key_length(unsigned int len)
+{
+	switch (len) {
+	case 256:
+	case 512:
+	case 1024:
+	case 2048:
+	case 4096:
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int eip28_rsa_setkey(struct eip28_rsa_ctx *ctx, const void *key,
+			    unsigned int key_len, bool private)
+{
+	struct rsa_key raw_key = {0};
+	int ret;
+
+	/* Parse the key elements: (n, e) for the public key, (n, d) for the private key */
+	if (private)
+		ret = rsa_parse_priv_key(&raw_key, key, key_len);
+	else
+		ret = rsa_parse_pub_key(&raw_key, key, key_len);
+	if (ret)
+		return ret;
+
+	/* Clean the buffers before copying the key elements */
+	eip28_rsa_clean_ctx(ctx);
+
+	/* Save the modulo 'n' */
+	ret = eip28_rsa_save_modulo(ctx, &raw_key);
+	if (ret)
+		goto clean_ctx;
+
+	ret = eip28_rsa_check_key_length(ctx->n_sz * 8);
+	if (ret)
+		goto clean_ctx;
+
+	/* Save the public exponent 'e' */
+	ret = eip28_rsa_save_keypart(&ctx->e, &ctx->e_sz,
+				     raw_key.e, raw_key.e_sz, ctx->n_sz);
+	if (ret)
+		goto clean_ctx;
+
+	/* Save the private exponent 'd', if known */
+	if (private) {
+		ret = eip28_rsa_save_keypart(&ctx->d, &ctx->d_sz,
+					     raw_key.d, raw_key.d_sz, ctx->n_sz);
+		if (ret)
+			goto clean_ctx;
+	}
+
+	/* Set the message buffer */
+	ctx->message = kzalloc(ctx->n_sz, GFP_KERNEL);
+	if (!ctx->message) {
+		ret = -ENOMEM;
+		goto clean_ctx;
+	}
+
+	return 0;
+
+clean_ctx:
+	eip28_rsa_clean_ctx(ctx);
+	return ret;
+}
+
+static int eip28_rsa_set_priv_key(struct crypto_akcipher *tfm, const void *key,
+				  unsigned int key_len)
+{
+	struct eip28_rsa_ctx *ctx = akcipher_tfm_ctx(tfm);
+
+	return eip28_rsa_setkey(ctx, key, key_len, true);
+}
+
+static int eip28_rsa_set_pub_key(struct crypto_akcipher *tfm, const void *key,
+				 unsigned int key_len)
+{
+	struct eip28_rsa_ctx *ctx = akcipher_tfm_ctx(tfm);
+
+	return eip28_rsa_setkey(ctx, key, key_len, false);
+}
+
+static int eip28_rsa_init(struct crypto_akcipher *tfm)
+{
+	struct eip28_rsa_ctx *ctx = akcipher_tfm_ctx(tfm);
+
+	ctx->priv = main_dev;
+
+	return 0;
+}
+
+static void eip28_rsa_exit(struct crypto_akcipher *tfm)
+{
+	struct eip28_rsa_ctx *ctx = akcipher_tfm_ctx(tfm);
+
+	eip28_rsa_clean_ctx(ctx);
+}
+
+struct akcipher_alg eip28_rsa = {
+	.encrypt = eip28_rsa_encrypt,
+	.decrypt = eip28_rsa_decrypt,
+	.set_pub_key = eip28_rsa_set_pub_key,
+	.set_priv_key = eip28_rsa_set_priv_key,
+	.max_size = eip28_rsa_max_size,
+	.init = eip28_rsa_init,
+	.exit = eip28_rsa_exit,
+	.base = {
+		.cra_name = "rsa",
+		.cra_driver_name = "eip28-rsa",
+		.cra_flags = CRYPTO_ALG_TYPE_AKCIPHER,
+		.cra_priority = 300,
+		.cra_module = THIS_MODULE,
+		.cra_ctxsize = sizeof(struct eip28_rsa_ctx),
+	},
+};
+
+static int eip28_check_firmware(struct eip28 *priv)
+{
+	u32 reg;
+
+	reg = readl(priv->regs + EIP28_PKA_REVISION);
+	if (EIP28_REV_MAJOR(reg) != 2) {
+		dev_err(priv->dev, "Unsupported hardware revision: %lu.%lu.%lu\n",
+			EIP28_REV_MAJOR(reg), EIP28_REV_MINOR(reg), EIP28_REV_PATCH_LVL(reg));
+		return -EINVAL;
+	}
+
+	reg = readl(priv->regs + EIP28_PKA_SW_REV);
+	if ((EIP28_REV_MAJOR(reg) != 2) ||
+	    (EIP28_REV_MINOR(reg) != 1) ||
+	    (EIP28_REV_PATCH_LVL(reg) != 1) ||
+	    (EIP28_REV_CAPABILITIES(reg) != 2)) {
+		dev_err(priv->dev, "Unsupported firmware revision: %lu.%lu.%lu.%lu\n",
+			EIP28_REV_MAJOR(reg), EIP28_REV_MINOR(reg),
+			EIP28_REV_PATCH_LVL(reg), EIP28_REV_CAPABILITIES(reg));
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int eip28_write_firmware(struct eip28 *priv, const struct firmware *eip28_fw)
+{
+	const u32 *data = (const u32 *)eip28_fw->data;
+	u32 reg;
+	int i;
+
+	if (eip28_fw->size > EIP28_PKA_PROGRAM_RAM_SZ)
+		return -EOPNOTSUPP;
+
+	/* Enter reset mode, which allows writing of PKA_PROGRAM_RAM registers */
+	writel(EIP28_CTRL_RESET, priv->regs + EIP28_PKA_SEQ_CTRL);
+	reg = readl(priv->regs + EIP28_PKA_SEQ_CTRL);
+	if (reg != EIP28_CTRL_RESET)
+		return -EIO;
+
+	for (i = 0; i < (eip28_fw->size / sizeof(u32)); i++)
+		writel(data[i], priv->regs + EIP28_PKA_PROGRAM_RAM + (i * 4));
+
+	/* Release reset mode, start the EIP28 */
+	writel(0, priv->regs + EIP28_PKA_SEQ_CTRL);
+	reg = readl(priv->regs + EIP28_PKA_SEQ_CTRL);
+	if (reg & EIP28_CTRL_RESET)
+		return -EIO;
+
+	return 0;
+}
+
+static int eip28_load_firmware(struct eip28 *priv)
+{
+	const struct firmware *eip28_fw;
+	int ret = 0;
+
+	ret = request_firmware(&eip28_fw, EIP28_FW_NAME, priv->dev);
+	if (ret)
+		return ret;
+
+	/* Write the EIP28 firmware in RAM CODE registers */
+	ret = eip28_write_firmware(priv, eip28_fw);
+	release_firmware(eip28_fw);
+	if (ret) {
+		dev_err(priv->dev, "Unable to write firmware (%d)\n", ret);
+		return ret;
+	}
+
+	/*
+	 * Once firmware is loaded, after releasing the reset bit the firmware
+	 * starts executing and after a few clock cycles it clears the RUN bit,
+	 * indicating initialization is done.
+	 */
+	ret = wait_for_completion_timeout(&priv->completion, msecs_to_jiffies(1000));
+	if (!ret) {
+		dev_err(priv->dev, "Firmware loading failed\n");
+		return -ETIMEDOUT;
+	}
+
+	/* Verify the firmware: can be read, is valid, has the correct version */
+	return eip28_check_firmware(priv);
+}
+
+static irqreturn_t eip28_irq_handler(int irq, void *dev_id)
+{
+	struct eip28 *priv = dev_id;
+
+	complete(&priv->completion);
+
+	return IRQ_HANDLED;
+}
+
+static int eip28_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct eip28 *priv;
+	int irq, ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(priv->regs))
+		return PTR_ERR(priv->regs);
+
+	priv->clk = devm_clk_get_enabled(dev, NULL);
+	if (IS_ERR(priv->clk))
+		return PTR_ERR(priv->regs);
+
+	irq = platform_get_irq(pdev, 2);
+	if (irq < 0)
+		return irq;
+
+	ret = devm_request_irq(dev, irq, eip28_irq_handler, 0, "eip28", priv);
+	if (ret)
+		return ret;
+
+	main_dev = priv;
+	priv->dev = dev;
+	init_completion(&priv->completion);
+	mutex_init(&priv->lock);
+
+	/* Initialize the code section of the internal RAM with the firmware */
+	ret = eip28_load_firmware(priv);
+	if (ret) {
+		dev_err(dev, "Unable to initialize EIP28 firmware (%d)\n", ret);
+		return ret;
+	}
+
+	/* Initializing crypto API for RSA keys */
+	ret = crypto_register_akcipher(&eip28_rsa);
+	if (ret) {
+		dev_err(dev, "Unable to initialize RSA\n");
+		return ret;
+	}
+
+	dev_info(dev, "EIP28 probed successfully\n");
+
+	return ret;
+}
+
+static void eip28_remove(struct platform_device *pdev)
+{
+	crypto_unregister_akcipher(&eip28_rsa);
+}
+
+static const struct of_device_id eip28_of_match_table[] = {
+	{ .compatible = "inside-secure,safexcel-eip28", },
+	{}
+};
+
+static struct platform_driver eip28_driver = {
+	.probe = eip28_probe,
+	.remove = eip28_remove,
+	.driver = {
+		.name = "Safexcel EIP28 PKA",
+		.of_match_table = eip28_of_match_table,
+	},
+};
+module_platform_driver(eip28_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pierre Castellan <pierre.castellan@non.se.com>");
+MODULE_AUTHOR("Thomas Ghesquiere <thomas.ghesquierre@non.se.com>");
+MODULE_AUTHOR("Mathieu Hadjimegrian <mathieu.hadjimegrian@non.se.com>");
+MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
+MODULE_DESCRIPTION("SafeXcel EIP28 Public Key Accelerator driver");
+MODULE_FIRMWARE(EIP28_FW_NAME);

-- 
2.51.1