// SPDX-License-Identifier: GPL-2.0 /* * sun8i-ce-core.c - hardware cryptographic offloader for * Allwinner H3/A64/H5/H2+/H6/R40 SoC * * Copyright (C) 2015-2019 Corentin Labbe * * Core file which registers crypto algorithms supported by the CryptoEngine. * * You could find a link for the datasheet in Documentation/arm/sunxi.rst */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sun8i-ce.h" /* * mod clock is lower on H3 than other SoC due to some DMA timeout occurring * with high value. * If you want to tune mod clock, loading driver and passing selftest is * insufficient, you need to test with some LUKS test (mount and write to it) */ static const struct ce_variant ce_h3_variant = { .alg_cipher = { CE_ALG_AES, CE_ALG_DES, CE_ALG_3DES, }, .alg_hash = { CE_ALG_MD5, CE_ALG_SHA1, CE_ALG_SHA224, CE_ALG_SHA256, CE_ALG_SHA384, CE_ALG_SHA512 }, .op_mode = { CE_OP_ECB, CE_OP_CBC }, .ce_clks = { { "bus", 0, 200000000 }, { "mod", 50000000, 0 }, }, .esr = ESR_H3, .prng = CE_ALG_PRNG, .trng = CE_ID_NOTSUPP, }; static const struct ce_variant ce_h5_variant = { .alg_cipher = { CE_ALG_AES, CE_ALG_DES, CE_ALG_3DES, }, .alg_hash = { CE_ALG_MD5, CE_ALG_SHA1, CE_ALG_SHA224, CE_ALG_SHA256, CE_ID_NOTSUPP, CE_ID_NOTSUPP }, .op_mode = { CE_OP_ECB, CE_OP_CBC }, .ce_clks = { { "bus", 0, 200000000 }, { "mod", 300000000, 0 }, }, .esr = ESR_H5, .prng = CE_ALG_PRNG, .trng = CE_ID_NOTSUPP, }; static const struct ce_variant ce_h6_variant = { .alg_cipher = { CE_ALG_AES, CE_ALG_DES, CE_ALG_3DES, }, .alg_hash = { CE_ALG_MD5, CE_ALG_SHA1, CE_ALG_SHA224, CE_ALG_SHA256, CE_ALG_SHA384, CE_ALG_SHA512 }, .op_mode = { CE_OP_ECB, CE_OP_CBC }, .cipher_t_dlen_in_bytes = true, .hash_t_dlen_in_bits = true, .prng_t_dlen_in_bytes = true, .trng_t_dlen_in_bytes = true, .ce_clks = { { "bus", 0, 200000000 }, { "mod", 300000000, 0 }, { "ram", 0, 400000000 }, }, .esr = ESR_H6, .prng = CE_ALG_PRNG_V2, .trng = CE_ALG_TRNG_V2, }; static const struct ce_variant ce_a64_variant = { .alg_cipher = { CE_ALG_AES, CE_ALG_DES, CE_ALG_3DES, }, .alg_hash = { CE_ALG_MD5, CE_ALG_SHA1, CE_ALG_SHA224, CE_ALG_SHA256, CE_ID_NOTSUPP, CE_ID_NOTSUPP }, .op_mode = { CE_OP_ECB, CE_OP_CBC }, .ce_clks = { { "bus", 0, 200000000 }, { "mod", 300000000, 0 }, }, .esr = ESR_A64, .prng = CE_ALG_PRNG, .trng = CE_ID_NOTSUPP, }; static const struct ce_variant ce_r40_variant = { .alg_cipher = { CE_ALG_AES, CE_ALG_DES, CE_ALG_3DES, }, .alg_hash = { CE_ALG_MD5, CE_ALG_SHA1, CE_ALG_SHA224, CE_ALG_SHA256, CE_ID_NOTSUPP, CE_ID_NOTSUPP }, .op_mode = { CE_OP_ECB, CE_OP_CBC }, .ce_clks = { { "bus", 0, 200000000 }, { "mod", 300000000, 0 }, }, .esr = ESR_R40, .prng = CE_ALG_PRNG, .trng = CE_ID_NOTSUPP, }; /* * sun8i_ce_get_engine_number() get the next channel slot * This is a simple round-robin way of getting the next channel * The flow 3 is reserve for xRNG operations */ int sun8i_ce_get_engine_number(struct sun8i_ce_dev *ce) { return atomic_inc_return(&ce->flow) % (MAXFLOW - 1); } int sun8i_ce_run_task(struct sun8i_ce_dev *ce, int flow, const char *name) { u32 v; int err = 0; struct ce_task *cet = ce->chanlist[flow].tl; #ifdef CONFIG_CRYPTO_DEV_SUN8I_CE_DEBUG ce->chanlist[flow].stat_req++; #endif mutex_lock(&ce->mlock); v = readl(ce->base + CE_ICR); v |= 1 << flow; writel(v, ce->base + CE_ICR); reinit_completion(&ce->chanlist[flow].complete); writel(ce->chanlist[flow].t_phy, ce->base + CE_TDQ); ce->chanlist[flow].status = 0; /* Be sure all data is written before enabling the task */ wmb(); /* Only H6 needs to write a part of t_common_ctl along with "1", but since it is ignored * on older SoCs, we have no reason to complicate things. */ v = 1 | ((le32_to_cpu(ce->chanlist[flow].tl->t_common_ctl) & 0x7F) << 8); writel(v, ce->base + CE_TLR); mutex_unlock(&ce->mlock); wait_for_completion_interruptible_timeout(&ce->chanlist[flow].complete, msecs_to_jiffies(ce->chanlist[flow].timeout)); if (ce->chanlist[flow].status == 0) { dev_err(ce->dev, "DMA timeout for %s (tm=%d) on flow %d\n", name, ce->chanlist[flow].timeout, flow); err = -EFAULT; } /* No need to lock for this read, the channel is locked so * nothing could modify the error value for this channel */ v = readl(ce->base + CE_ESR); switch (ce->variant->esr) { case ESR_H3: /* Sadly, the error bit is not per flow */ if (v) { dev_err(ce->dev, "CE ERROR: %x for flow %x\n", v, flow); err = -EFAULT; print_hex_dump(KERN_INFO, "TASK: ", DUMP_PREFIX_NONE, 16, 4, cet, sizeof(struct ce_task), false); } if (v & CE_ERR_ALGO_NOTSUP) dev_err(ce->dev, "CE ERROR: algorithm not supported\n"); if (v & CE_ERR_DATALEN) dev_err(ce->dev, "CE ERROR: data length error\n"); if (v & CE_ERR_KEYSRAM) dev_err(ce->dev, "CE ERROR: keysram access error for AES\n"); break; case ESR_A64: case ESR_H5: case ESR_R40: v >>= (flow * 4); v &= 0xF; if (v) { dev_err(ce->dev, "CE ERROR: %x for flow %x\n", v, flow); err = -EFAULT; print_hex_dump(KERN_INFO, "TASK: ", DUMP_PREFIX_NONE, 16, 4, cet, sizeof(struct ce_task), false); } if (v & CE_ERR_ALGO_NOTSUP) dev_err(ce->dev, "CE ERROR: algorithm not supported\n"); if (v & CE_ERR_DATALEN) dev_err(ce->dev, "CE ERROR: data length error\n"); if (v & CE_ERR_KEYSRAM) dev_err(ce->dev, "CE ERROR: keysram access error for AES\n"); break; case ESR_H6: v >>= (flow * 8); v &= 0xFF; if (v) { dev_err(ce->dev, "CE ERROR: %x for flow %x\n", v, flow); err = -EFAULT; print_hex_dump(KERN_INFO, "TASK: ", DUMP_PREFIX_NONE, 16, 4, cet, sizeof(struct ce_task), false); } if (v & CE_ERR_ALGO_NOTSUP) dev_err(ce->dev, "CE ERROR: algorithm not supported\n"); if (v & CE_ERR_DATALEN) dev_err(ce->dev, "CE ERROR: data length error\n"); if (v & CE_ERR_KEYSRAM) dev_err(ce->dev, "CE ERROR: keysram access error for AES\n"); if (v & CE_ERR_ADDR_INVALID) dev_err(ce->dev, "CE ERROR: address invalid\n"); if (v & CE_ERR_KEYLADDER) dev_err(ce->dev, "CE ERROR: key ladder configuration error\n"); break; } return err; } static irqreturn_t ce_irq_handler(int irq, void *data) { struct sun8i_ce_dev *ce = (struct sun8i_ce_dev *)data; int flow = 0; u32 p; p = readl(ce->base + CE_ISR); for (flow = 0; flow < MAXFLOW; flow++) { if (p & (BIT(flow))) { writel(BIT(flow), ce->base + CE_ISR); ce->chanlist[flow].status = 1; complete(&ce->chanlist[flow].complete); } } return IRQ_HANDLED; } static struct sun8i_ce_alg_template ce_algs[] = { { .type = CRYPTO_ALG_TYPE_SKCIPHER, .ce_algo_id = CE_ID_CIPHER_AES, .ce_blockmode = CE_ID_OP_CBC, .alg.skcipher = { .base = { .cra_name = "cbc(aes)", .cra_driver_name = "cbc-aes-sun8i-ce", .cra_priority = 400, .cra_blocksize = AES_BLOCK_SIZE, .cra_flags = CRYPTO_ALG_TYPE_SKCIPHER | CRYPTO_ALG_ASYNC | CRYPTO_ALG_ALLOCATES_MEMORY | CRYPTO_ALG_NEED_FALLBACK, .cra_ctxsize = sizeof(struct sun8i_cipher_tfm_ctx), .cra_module = THIS_MODULE, .cra_alignmask = 0xf, .cra_init = sun8i_ce_cipher_init, .cra_exit = sun8i_ce_cipher_exit, }, .min_keysize = AES_MIN_KEY_SIZE, .max_keysize = AES_MAX_KEY_SIZE, .ivsize = AES_BLOCK_SIZE, .setkey = sun8i_ce_aes_setkey, .encrypt = sun8i_ce_skencrypt, .decrypt = sun8i_ce_skdecrypt, } }, { .type = CRYPTO_ALG_TYPE_SKCIPHER, .ce_algo_id = CE_ID_CIPHER_AES, .ce_blockmode = CE_ID_OP_ECB, .alg.skcipher = { .base = { .cra_name = "ecb(aes)", .cra_driver_name = "ecb-aes-sun8i-ce", .cra_priority = 400, .cra_blocksize = AES_BLOCK_SIZE, .cra_flags = CRYPTO_ALG_TYPE_SKCIPHER | CRYPTO_ALG_ASYNC | CRYPTO_ALG_ALLOCATES_MEMORY | CRYPTO_ALG_NEED_FALLBACK, .cra_ctxsize = sizeof(struct sun8i_cipher_tfm_ctx), .cra_module = THIS_MODULE, .cra_alignmask = 0xf, .cra_init = sun8i_ce_cipher_init, .cra_exit = sun8i_ce_cipher_exit, }, .min_keysize = AES_MIN_KEY_SIZE, .max_keysize = AES_MAX_KEY_SIZE, .setkey = sun8i_ce_aes_setkey, .encrypt = sun8i_ce_skencrypt, .decrypt = sun8i_ce_skdecrypt, } }, { .type = CRYPTO_ALG_TYPE_SKCIPHER, .ce_algo_id = CE_ID_CIPHER_DES3, .ce_blockmode = CE_ID_OP_CBC, .alg.skcipher = { .base = { .cra_name = "cbc(des3_ede)", .cra_driver_name = "cbc-des3-sun8i-ce", .cra_priority = 400, .cra_blocksize = DES3_EDE_BLOCK_SIZE, .cra_flags = CRYPTO_ALG_TYPE_SKCIPHER | CRYPTO_ALG_ASYNC | CRYPTO_ALG_ALLOCATES_MEMORY | CRYPTO_ALG_NEED_FALLBACK, .cra_ctxsize = sizeof(struct sun8i_cipher_tfm_ctx), .cra_module = THIS_MODULE, .cra_alignmask = 0xf, .cra_init = sun8i_ce_cipher_init, .cra_exit = sun8i_ce_cipher_exit, }, .min_keysize = DES3_EDE_KEY_SIZE, .max_keysize = DES3_EDE_KEY_SIZE, .ivsize = DES3_EDE_BLOCK_SIZE, .setkey = sun8i_ce_des3_setkey, .encrypt = sun8i_ce_skencrypt, .decrypt = sun8i_ce_skdecrypt, } }, { .type = CRYPTO_ALG_TYPE_SKCIPHER, .ce_algo_id = CE_ID_CIPHER_DES3, .ce_blockmode = CE_ID_OP_ECB, .alg.skcipher = { .base = { .cra_name = "ecb(des3_ede)", .cra_driver_name = "ecb-des3-sun8i-ce", .cra_priority = 400, .cra_blocksize = DES3_EDE_BLOCK_SIZE, .cra_flags = CRYPTO_ALG_TYPE_SKCIPHER | CRYPTO_ALG_ASYNC | CRYPTO_ALG_ALLOCATES_MEMORY | CRYPTO_ALG_NEED_FALLBACK, .cra_ctxsize = sizeof(struct sun8i_cipher_tfm_ctx), .cra_module = THIS_MODULE, .cra_alignmask = 0xf, .cra_init = sun8i_ce_cipher_init, .cra_exit = sun8i_ce_cipher_exit, }, .min_keysize = DES3_EDE_KEY_SIZE, .max_keysize = DES3_EDE_KEY_SIZE, .setkey = sun8i_ce_des3_setkey, .encrypt = sun8i_ce_skencrypt, .decrypt = sun8i_ce_skdecrypt, } }, #ifdef CONFIG_CRYPTO_DEV_SUN8I_CE_HASH { .type = CRYPTO_ALG_TYPE_AHASH, .ce_algo_id = CE_ID_HASH_MD5, .alg.hash = { .init = sun8i_ce_hash_init, .update = sun8i_ce_hash_update, .final = sun8i_ce_hash_final, .finup = sun8i_ce_hash_finup, .digest = sun8i_ce_hash_digest, .export = sun8i_ce_hash_export, .import = sun8i_ce_hash_import, .halg = { .digestsize = MD5_DIGEST_SIZE, .statesize = sizeof(struct md5_state), .base = { .cra_name = "md5", .cra_driver_name = "md5-sun8i-ce", .cra_priority = 300, .cra_alignmask = 3, .cra_flags = CRYPTO_ALG_TYPE_AHASH | CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK, .cra_blocksize = MD5_HMAC_BLOCK_SIZE, .cra_ctxsize = sizeof(struct sun8i_ce_hash_tfm_ctx), .cra_module = THIS_MODULE, .cra_init = sun8i_ce_hash_crainit, .cra_exit = sun8i_ce_hash_craexit, } } } }, { .type = CRYPTO_ALG_TYPE_AHASH, .ce_algo_id = CE_ID_HASH_SHA1, .alg.hash = { .init = sun8i_ce_hash_init, .update = sun8i_ce_hash_update, .final = sun8i_ce_hash_final, .finup = sun8i_ce_hash_finup, .digest = sun8i_ce_hash_digest, .export = sun8i_ce_hash_export, .import = sun8i_ce_hash_import, .halg = { .digestsize = SHA1_DIGEST_SIZE, .statesize = sizeof(struct sha1_state), .base = { .cra_name = "sha1", .cra_driver_name = "sha1-sun8i-ce", .cra_priority = 300, .cra_alignmask = 3, .cra_flags = CRYPTO_ALG_TYPE_AHASH | CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK, .cra_blocksize = SHA1_BLOCK_SIZE, .cra_ctxsize = sizeof(struct sun8i_ce_hash_tfm_ctx), .cra_module = THIS_MODULE, .cra_init = sun8i_ce_hash_crainit, .cra_exit = sun8i_ce_hash_craexit, } } } }, { .type = CRYPTO_ALG_TYPE_AHASH, .ce_algo_id = CE_ID_HASH_SHA224, .alg.hash = { .init = sun8i_ce_hash_init, .update = sun8i_ce_hash_update, .final = sun8i_ce_hash_final, .finup = sun8i_ce_hash_finup, .digest = sun8i_ce_hash_digest, .export = sun8i_ce_hash_export, .import = sun8i_ce_hash_import, .halg = { .digestsize = SHA224_DIGEST_SIZE, .statesize = sizeof(struct sha256_state), .base = { .cra_name = "sha224", .cra_driver_name = "sha224-sun8i-ce", .cra_priority = 300, .cra_alignmask = 3, .cra_flags = CRYPTO_ALG_TYPE_AHASH | CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK, .cra_blocksize = SHA224_BLOCK_SIZE, .cra_ctxsize = sizeof(struct sun8i_ce_hash_tfm_ctx), .cra_module = THIS_MODULE, .cra_init = sun8i_ce_hash_crainit, .cra_exit = sun8i_ce_hash_craexit, } } } }, { .type = CRYPTO_ALG_TYPE_AHASH, .ce_algo_id = CE_ID_HASH_SHA256, .alg.hash = { .init = sun8i_ce_hash_init, .update = sun8i_ce_hash_update, .final = sun8i_ce_hash_final, .finup = sun8i_ce_hash_finup, .digest = sun8i_ce_hash_digest, .export = sun8i_ce_hash_export, .import = sun8i_ce_hash_import, .halg = { .digestsize = SHA256_DIGEST_SIZE, .statesize = sizeof(struct sha256_state), .base = { .cra_name = "sha256", .cra_driver_name = "sha256-sun8i-ce", .cra_priority = 300, .cra_alignmask = 3, .cra_flags = CRYPTO_ALG_TYPE_AHASH | CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK, .cra_blocksize = SHA256_BLOCK_SIZE, .cra_ctxsize = sizeof(struct sun8i_ce_hash_tfm_ctx), .cra_module = THIS_MODULE, .cra_init = sun8i_ce_hash_crainit, .cra_exit = sun8i_ce_hash_craexit, } } } }, { .type = CRYPTO_ALG_TYPE_AHASH, .ce_algo_id = CE_ID_HASH_SHA384, .alg.hash = { .init = sun8i_ce_hash_init, .update = sun8i_ce_hash_update, .final = sun8i_ce_hash_final, .finup = sun8i_ce_hash_finup, .digest = sun8i_ce_hash_digest, .export = sun8i_ce_hash_export, .import = sun8i_ce_hash_import, .halg = { .digestsize = SHA384_DIGEST_SIZE, .statesize = sizeof(struct sha512_state), .base = { .cra_name = "sha384", .cra_driver_name = "sha384-sun8i-ce", .cra_priority = 300, .cra_alignmask = 3, .cra_flags = CRYPTO_ALG_TYPE_AHASH | CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK, .cra_blocksize = SHA384_BLOCK_SIZE, .cra_ctxsize = sizeof(struct sun8i_ce_hash_tfm_ctx), .cra_module = THIS_MODULE, .cra_init = sun8i_ce_hash_crainit, .cra_exit = sun8i_ce_hash_craexit, } } } }, { .type = CRYPTO_ALG_TYPE_AHASH, .ce_algo_id = CE_ID_HASH_SHA512, .alg.hash = { .init = sun8i_ce_hash_init, .update = sun8i_ce_hash_update, .final = sun8i_ce_hash_final, .finup = sun8i_ce_hash_finup, .digest = sun8i_ce_hash_digest, .export = sun8i_ce_hash_export, .import = sun8i_ce_hash_import, .halg = { .digestsize = SHA512_DIGEST_SIZE, .statesize = sizeof(struct sha512_state), .base = { .cra_name = "sha512", .cra_driver_name = "sha512-sun8i-ce", .cra_priority = 300, .cra_alignmask = 3, .cra_flags = CRYPTO_ALG_TYPE_AHASH | CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK, .cra_blocksize = SHA512_BLOCK_SIZE, .cra_ctxsize = sizeof(struct sun8i_ce_hash_tfm_ctx), .cra_module = THIS_MODULE, .cra_init = sun8i_ce_hash_crainit, .cra_exit = sun8i_ce_hash_craexit, } } } }, #endif #ifdef CONFIG_CRYPTO_DEV_SUN8I_CE_PRNG { .type = CRYPTO_ALG_TYPE_RNG, .alg.rng = { .base = { .cra_name = "stdrng", .cra_driver_name = "sun8i-ce-prng", .cra_priority = 300, .cra_ctxsize = sizeof(struct sun8i_ce_rng_tfm_ctx), .cra_module = THIS_MODULE, .cra_init = sun8i_ce_prng_init, .cra_exit = sun8i_ce_prng_exit, }, .generate = sun8i_ce_prng_generate, .seed = sun8i_ce_prng_seed, .seedsize = PRNG_SEED_SIZE, } }, #endif }; #ifdef CONFIG_CRYPTO_DEV_SUN8I_CE_DEBUG static int sun8i_ce_debugfs_show(struct seq_file *seq, void *v) { struct sun8i_ce_dev *ce = seq->private; unsigned int i; for (i = 0; i < MAXFLOW; i++) seq_printf(seq, "Channel %d: nreq %lu\n", i, ce->chanlist[i].stat_req); for (i = 0; i < ARRAY_SIZE(ce_algs); i++) { if (!ce_algs[i].ce) continue; switch (ce_algs[i].type) { case CRYPTO_ALG_TYPE_SKCIPHER: seq_printf(seq, "%s %s %lu %lu\n", ce_algs[i].alg.skcipher.base.cra_driver_name, ce_algs[i].alg.skcipher.base.cra_name, ce_algs[i].stat_req, ce_algs[i].stat_fb); break; case CRYPTO_ALG_TYPE_AHASH: seq_printf(seq, "%s %s %lu %lu\n", ce_algs[i].alg.hash.halg.base.cra_driver_name, ce_algs[i].alg.hash.halg.base.cra_name, ce_algs[i].stat_req, ce_algs[i].stat_fb); break; case CRYPTO_ALG_TYPE_RNG: seq_printf(seq, "%s %s %lu %lu\n", ce_algs[i].alg.rng.base.cra_driver_name, ce_algs[i].alg.rng.base.cra_name, ce_algs[i].stat_req, ce_algs[i].stat_bytes); break; } } #ifdef CONFIG_CRYPTO_DEV_SUN8I_CE_TRNG seq_printf(seq, "HWRNG %lu %lu\n", ce->hwrng_stat_req, ce->hwrng_stat_bytes); #endif return 0; } DEFINE_SHOW_ATTRIBUTE(sun8i_ce_debugfs); #endif static void sun8i_ce_free_chanlist(struct sun8i_ce_dev *ce, int i) { while (i >= 0) { crypto_engine_exit(ce->chanlist[i].engine); if (ce->chanlist[i].tl) dma_free_coherent(ce->dev, sizeof(struct ce_task), ce->chanlist[i].tl, ce->chanlist[i].t_phy); i--; } } /* * Allocate the channel list structure */ static int sun8i_ce_allocate_chanlist(struct sun8i_ce_dev *ce) { int i, err; ce->chanlist = devm_kcalloc(ce->dev, MAXFLOW, sizeof(struct sun8i_ce_flow), GFP_KERNEL); if (!ce->chanlist) return -ENOMEM; for (i = 0; i < MAXFLOW; i++) { init_completion(&ce->chanlist[i].complete); ce->chanlist[i].engine = crypto_engine_alloc_init(ce->dev, true); if (!ce->chanlist[i].engine) { dev_err(ce->dev, "Cannot allocate engine\n"); i--; err = -ENOMEM; goto error_engine; } err = crypto_engine_start(ce->chanlist[i].engine); if (err) { dev_err(ce->dev, "Cannot start engine\n"); goto error_engine; } ce->chanlist[i].tl = dma_alloc_coherent(ce->dev, sizeof(struct ce_task), &ce->chanlist[i].t_phy, GFP_KERNEL); if (!ce->chanlist[i].tl) { dev_err(ce->dev, "Cannot get DMA memory for task %d\n", i); err = -ENOMEM; goto error_engine; } } return 0; error_engine: sun8i_ce_free_chanlist(ce, i); return err; } /* * Power management strategy: The device is suspended unless a TFM exists for * one of the algorithms proposed by this driver. */ static int sun8i_ce_pm_suspend(struct device *dev) { struct sun8i_ce_dev *ce = dev_get_drvdata(dev); int i; reset_control_assert(ce->reset); for (i = 0; i < CE_MAX_CLOCKS; i++) clk_disable_unprepare(ce->ceclks[i]); return 0; } static int sun8i_ce_pm_resume(struct device *dev) { struct sun8i_ce_dev *ce = dev_get_drvdata(dev); int err, i; for (i = 0; i < CE_MAX_CLOCKS; i++) { if (!ce->variant->ce_clks[i].name) continue; err = clk_prepare_enable(ce->ceclks[i]); if (err) { dev_err(ce->dev, "Cannot prepare_enable %s\n", ce->variant->ce_clks[i].name); goto error; } } err = reset_control_deassert(ce->reset); if (err) { dev_err(ce->dev, "Cannot deassert reset control\n"); goto error; } return 0; error: sun8i_ce_pm_suspend(dev); return err; } static const struct dev_pm_ops sun8i_ce_pm_ops = { SET_RUNTIME_PM_OPS(sun8i_ce_pm_suspend, sun8i_ce_pm_resume, NULL) }; static int sun8i_ce_pm_init(struct sun8i_ce_dev *ce) { int err; pm_runtime_use_autosuspend(ce->dev); pm_runtime_set_autosuspend_delay(ce->dev, 2000); err = pm_runtime_set_suspended(ce->dev); if (err) return err; pm_runtime_enable(ce->dev); return err; } static void sun8i_ce_pm_exit(struct sun8i_ce_dev *ce) { pm_runtime_disable(ce->dev); } static int sun8i_ce_get_clks(struct sun8i_ce_dev *ce) { unsigned long cr; int err, i; for (i = 0; i < CE_MAX_CLOCKS; i++) { if (!ce->variant->ce_clks[i].name) continue; ce->ceclks[i] = devm_clk_get(ce->dev, ce->variant->ce_clks[i].name); if (IS_ERR(ce->ceclks[i])) { err = PTR_ERR(ce->ceclks[i]); dev_err(ce->dev, "Cannot get %s CE clock err=%d\n", ce->variant->ce_clks[i].name, err); return err; } cr = clk_get_rate(ce->ceclks[i]); if (!cr) return -EINVAL; if (ce->variant->ce_clks[i].freq > 0 && cr != ce->variant->ce_clks[i].freq) { dev_info(ce->dev, "Set %s clock to %lu (%lu Mhz) from %lu (%lu Mhz)\n", ce->variant->ce_clks[i].name, ce->variant->ce_clks[i].freq, ce->variant->ce_clks[i].freq / 1000000, cr, cr / 1000000); err = clk_set_rate(ce->ceclks[i], ce->variant->ce_clks[i].freq); if (err) dev_err(ce->dev, "Fail to set %s clk speed to %lu hz\n", ce->variant->ce_clks[i].name, ce->variant->ce_clks[i].freq); } if (ce->variant->ce_clks[i].max_freq > 0 && cr > ce->variant->ce_clks[i].max_freq) dev_warn(ce->dev, "Frequency for %s (%lu hz) is higher than datasheet's recommendation (%lu hz)", ce->variant->ce_clks[i].name, cr, ce->variant->ce_clks[i].max_freq); } return 0; } static int sun8i_ce_register_algs(struct sun8i_ce_dev *ce) { int ce_method, err, id; unsigned int i; for (i = 0; i < ARRAY_SIZE(ce_algs); i++) { ce_algs[i].ce = ce; switch (ce_algs[i].type) { case CRYPTO_ALG_TYPE_SKCIPHER: id = ce_algs[i].ce_algo_id; ce_method = ce->variant->alg_cipher[id]; if (ce_method == CE_ID_NOTSUPP) { dev_dbg(ce->dev, "DEBUG: Algo of %s not supported\n", ce_algs[i].alg.skcipher.base.cra_name); ce_algs[i].ce = NULL; break; } id = ce_algs[i].ce_blockmode; ce_method = ce->variant->op_mode[id]; if (ce_method == CE_ID_NOTSUPP) { dev_dbg(ce->dev, "DEBUG: Blockmode of %s not supported\n", ce_algs[i].alg.skcipher.base.cra_name); ce_algs[i].ce = NULL; break; } dev_info(ce->dev, "Register %s\n", ce_algs[i].alg.skcipher.base.cra_name); err = crypto_register_skcipher(&ce_algs[i].alg.skcipher); if (err) { dev_err(ce->dev, "ERROR: Fail to register %s\n", ce_algs[i].alg.skcipher.base.cra_name); ce_algs[i].ce = NULL; return err; } break; case CRYPTO_ALG_TYPE_AHASH: id = ce_algs[i].ce_algo_id; ce_method = ce->variant->alg_hash[id]; if (ce_method == CE_ID_NOTSUPP) { dev_info(ce->dev, "DEBUG: Algo of %s not supported\n", ce_algs[i].alg.hash.halg.base.cra_name); ce_algs[i].ce = NULL; break; } dev_info(ce->dev, "Register %s\n", ce_algs[i].alg.hash.halg.base.cra_name); err = crypto_register_ahash(&ce_algs[i].alg.hash); if (err) { dev_err(ce->dev, "ERROR: Fail to register %s\n", ce_algs[i].alg.hash.halg.base.cra_name); ce_algs[i].ce = NULL; return err; } break; case CRYPTO_ALG_TYPE_RNG: if (ce->variant->prng == CE_ID_NOTSUPP) { dev_info(ce->dev, "DEBUG: Algo of %s not supported\n", ce_algs[i].alg.rng.base.cra_name); ce_algs[i].ce = NULL; break; } dev_info(ce->dev, "Register %s\n", ce_algs[i].alg.rng.base.cra_name); err = crypto_register_rng(&ce_algs[i].alg.rng); if (err) { dev_err(ce->dev, "Fail to register %s\n", ce_algs[i].alg.rng.base.cra_name); ce_algs[i].ce = NULL; } break; default: ce_algs[i].ce = NULL; dev_err(ce->dev, "ERROR: tried to register an unknown algo\n"); } } return 0; } static void sun8i_ce_unregister_algs(struct sun8i_ce_dev *ce) { unsigned int i; for (i = 0; i < ARRAY_SIZE(ce_algs); i++) { if (!ce_algs[i].ce) continue; switch (ce_algs[i].type) { case CRYPTO_ALG_TYPE_SKCIPHER: dev_info(ce->dev, "Unregister %d %s\n", i, ce_algs[i].alg.skcipher.base.cra_name); crypto_unregister_skcipher(&ce_algs[i].alg.skcipher); break; case CRYPTO_ALG_TYPE_AHASH: dev_info(ce->dev, "Unregister %d %s\n", i, ce_algs[i].alg.hash.halg.base.cra_name); crypto_unregister_ahash(&ce_algs[i].alg.hash); break; case CRYPTO_ALG_TYPE_RNG: dev_info(ce->dev, "Unregister %d %s\n", i, ce_algs[i].alg.rng.base.cra_name); crypto_unregister_rng(&ce_algs[i].alg.rng); break; } } } static int sun8i_ce_probe(struct platform_device *pdev) { struct sun8i_ce_dev *ce; int err, irq; u32 v; ce = devm_kzalloc(&pdev->dev, sizeof(*ce), GFP_KERNEL); if (!ce) return -ENOMEM; ce->dev = &pdev->dev; platform_set_drvdata(pdev, ce); ce->variant = of_device_get_match_data(&pdev->dev); if (!ce->variant) { dev_err(&pdev->dev, "Missing Crypto Engine variant\n"); return -EINVAL; } ce->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(ce->base)) return PTR_ERR(ce->base); err = sun8i_ce_get_clks(ce); if (err) return err; /* Get Non Secure IRQ */ irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; ce->reset = devm_reset_control_get(&pdev->dev, NULL); if (IS_ERR(ce->reset)) return dev_err_probe(&pdev->dev, PTR_ERR(ce->reset), "No reset control found\n"); mutex_init(&ce->mlock); mutex_init(&ce->rnglock); err = sun8i_ce_allocate_chanlist(ce); if (err) return err; err = sun8i_ce_pm_init(ce); if (err) goto error_pm; err = devm_request_irq(&pdev->dev, irq, ce_irq_handler, 0, "sun8i-ce-ns", ce); if (err) { dev_err(ce->dev, "Cannot request CryptoEngine Non-secure IRQ (err=%d)\n", err); goto error_irq; } err = sun8i_ce_register_algs(ce); if (err) goto error_alg; err = pm_runtime_get_sync(ce->dev); if (err < 0) goto error_alg; #ifdef CONFIG_CRYPTO_DEV_SUN8I_CE_TRNG sun8i_ce_hwrng_register(ce); #endif v = readl(ce->base + CE_CTR); v >>= CE_DIE_ID_SHIFT; v &= CE_DIE_ID_MASK; dev_info(&pdev->dev, "CryptoEngine Die ID %x\n", v); pm_runtime_put_sync(ce->dev); #ifdef CONFIG_CRYPTO_DEV_SUN8I_CE_DEBUG /* Ignore error of debugfs */ ce->dbgfs_dir = debugfs_create_dir("sun8i-ce", NULL); ce->dbgfs_stats = debugfs_create_file("stats", 0444, ce->dbgfs_dir, ce, &sun8i_ce_debugfs_fops); #endif return 0; error_alg: sun8i_ce_unregister_algs(ce); error_irq: sun8i_ce_pm_exit(ce); error_pm: sun8i_ce_free_chanlist(ce, MAXFLOW - 1); return err; } static int sun8i_ce_remove(struct platform_device *pdev) { struct sun8i_ce_dev *ce = platform_get_drvdata(pdev); #ifdef CONFIG_CRYPTO_DEV_SUN8I_CE_TRNG sun8i_ce_hwrng_unregister(ce); #endif sun8i_ce_unregister_algs(ce); #ifdef CONFIG_CRYPTO_DEV_SUN8I_CE_DEBUG debugfs_remove_recursive(ce->dbgfs_dir); #endif sun8i_ce_free_chanlist(ce, MAXFLOW - 1); sun8i_ce_pm_exit(ce); return 0; } static const struct of_device_id sun8i_ce_crypto_of_match_table[] = { { .compatible = "allwinner,sun8i-h3-crypto", .data = &ce_h3_variant }, { .compatible = "allwinner,sun8i-r40-crypto", .data = &ce_r40_variant }, { .compatible = "allwinner,sun50i-a64-crypto", .data = &ce_a64_variant }, { .compatible = "allwinner,sun50i-h5-crypto", .data = &ce_h5_variant }, { .compatible = "allwinner,sun50i-h6-crypto", .data = &ce_h6_variant }, {} }; MODULE_DEVICE_TABLE(of, sun8i_ce_crypto_of_match_table); static struct platform_driver sun8i_ce_driver = { .probe = sun8i_ce_probe, .remove = sun8i_ce_remove, .driver = { .name = "sun8i-ce", .pm = &sun8i_ce_pm_ops, .of_match_table = sun8i_ce_crypto_of_match_table, }, }; module_platform_driver(sun8i_ce_driver); MODULE_DESCRIPTION("Allwinner Crypto Engine cryptographic offloader"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Corentin Labbe ");