// SPDX-License-Identifier: GPL-2.0 /* * amlgoic-core.c - hardware cryptographic offloader for Amlogic GXL SoC * * Copyright (C) 2018-2019 Corentin Labbe * * Core file which registers crypto algorithms supported by the hardware. */ #include #include #include #include #include #include #include #include #include #include #include #include "amlogic-gxl.h" static irqreturn_t meson_irq_handler(int irq, void *data) { struct meson_dev *mc = (struct meson_dev *)data; int flow; u32 p; for (flow = 0; flow < MAXFLOW; flow++) { if (mc->irqs[flow] == irq) { p = readl(mc->base + ((0x04 + flow) << 2)); if (p) { writel_relaxed(0xF, mc->base + ((0x4 + flow) << 2)); mc->chanlist[flow].status = 1; complete(&mc->chanlist[flow].complete); return IRQ_HANDLED; } dev_err(mc->dev, "%s %d Got irq for flow %d but ctrl is empty\n", __func__, irq, flow); } } dev_err(mc->dev, "%s %d from unknown irq\n", __func__, irq); return IRQ_HANDLED; } static struct meson_alg_template mc_algs[] = { { .type = CRYPTO_ALG_TYPE_SKCIPHER, .blockmode = MESON_OPMODE_CBC, .alg.skcipher = { .base = { .cra_name = "cbc(aes)", .cra_driver_name = "cbc-aes-gxl", .cra_priority = 400, .cra_blocksize = AES_BLOCK_SIZE, .cra_flags = CRYPTO_ALG_TYPE_SKCIPHER | CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK, .cra_ctxsize = sizeof(struct meson_cipher_tfm_ctx), .cra_module = THIS_MODULE, .cra_alignmask = 0xf, .cra_init = meson_cipher_init, .cra_exit = meson_cipher_exit, }, .min_keysize = AES_MIN_KEY_SIZE, .max_keysize = AES_MAX_KEY_SIZE, .ivsize = AES_BLOCK_SIZE, .setkey = meson_aes_setkey, .encrypt = meson_skencrypt, .decrypt = meson_skdecrypt, } }, { .type = CRYPTO_ALG_TYPE_SKCIPHER, .blockmode = MESON_OPMODE_ECB, .alg.skcipher = { .base = { .cra_name = "ecb(aes)", .cra_driver_name = "ecb-aes-gxl", .cra_priority = 400, .cra_blocksize = AES_BLOCK_SIZE, .cra_flags = CRYPTO_ALG_TYPE_SKCIPHER | CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK, .cra_ctxsize = sizeof(struct meson_cipher_tfm_ctx), .cra_module = THIS_MODULE, .cra_alignmask = 0xf, .cra_init = meson_cipher_init, .cra_exit = meson_cipher_exit, }, .min_keysize = AES_MIN_KEY_SIZE, .max_keysize = AES_MAX_KEY_SIZE, .setkey = meson_aes_setkey, .encrypt = meson_skencrypt, .decrypt = meson_skdecrypt, } }, }; #ifdef CONFIG_CRYPTO_DEV_AMLOGIC_GXL_DEBUG static int meson_dbgfs_read(struct seq_file *seq, void *v) { struct meson_dev *mc = seq->private; int i; for (i = 0; i < MAXFLOW; i++) seq_printf(seq, "Channel %d: nreq %lu\n", i, mc->chanlist[i].stat_req); for (i = 0; i < ARRAY_SIZE(mc_algs); i++) { switch (mc_algs[i].type) { case CRYPTO_ALG_TYPE_SKCIPHER: seq_printf(seq, "%s %s %lu %lu\n", mc_algs[i].alg.skcipher.base.cra_driver_name, mc_algs[i].alg.skcipher.base.cra_name, mc_algs[i].stat_req, mc_algs[i].stat_fb); break; } } return 0; } static int meson_dbgfs_open(struct inode *inode, struct file *file) { return single_open(file, meson_dbgfs_read, inode->i_private); } static const struct file_operations meson_debugfs_fops = { .owner = THIS_MODULE, .open = meson_dbgfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; #endif static void meson_free_chanlist(struct meson_dev *mc, int i) { while (i >= 0) { crypto_engine_exit(mc->chanlist[i].engine); if (mc->chanlist[i].tl) dma_free_coherent(mc->dev, sizeof(struct meson_desc) * MAXDESC, mc->chanlist[i].tl, mc->chanlist[i].t_phy); i--; } } /* * Allocate the channel list structure */ static int meson_allocate_chanlist(struct meson_dev *mc) { int i, err; mc->chanlist = devm_kcalloc(mc->dev, MAXFLOW, sizeof(struct meson_flow), GFP_KERNEL); if (!mc->chanlist) return -ENOMEM; for (i = 0; i < MAXFLOW; i++) { init_completion(&mc->chanlist[i].complete); mc->chanlist[i].engine = crypto_engine_alloc_init(mc->dev, true); if (!mc->chanlist[i].engine) { dev_err(mc->dev, "Cannot allocate engine\n"); i--; err = -ENOMEM; goto error_engine; } err = crypto_engine_start(mc->chanlist[i].engine); if (err) { dev_err(mc->dev, "Cannot start engine\n"); goto error_engine; } mc->chanlist[i].tl = dma_alloc_coherent(mc->dev, sizeof(struct meson_desc) * MAXDESC, &mc->chanlist[i].t_phy, GFP_KERNEL); if (!mc->chanlist[i].tl) { err = -ENOMEM; goto error_engine; } } return 0; error_engine: meson_free_chanlist(mc, i); return err; } static int meson_register_algs(struct meson_dev *mc) { int err, i; for (i = 0; i < ARRAY_SIZE(mc_algs); i++) { mc_algs[i].mc = mc; switch (mc_algs[i].type) { case CRYPTO_ALG_TYPE_SKCIPHER: err = crypto_register_skcipher(&mc_algs[i].alg.skcipher); if (err) { dev_err(mc->dev, "Fail to register %s\n", mc_algs[i].alg.skcipher.base.cra_name); mc_algs[i].mc = NULL; return err; } break; } } return 0; } static void meson_unregister_algs(struct meson_dev *mc) { int i; for (i = 0; i < ARRAY_SIZE(mc_algs); i++) { if (!mc_algs[i].mc) continue; switch (mc_algs[i].type) { case CRYPTO_ALG_TYPE_SKCIPHER: crypto_unregister_skcipher(&mc_algs[i].alg.skcipher); break; } } } static int meson_crypto_probe(struct platform_device *pdev) { struct meson_dev *mc; int err, i; if (!pdev->dev.of_node) return -ENODEV; mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL); if (!mc) return -ENOMEM; mc->dev = &pdev->dev; platform_set_drvdata(pdev, mc); mc->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(mc->base)) { err = PTR_ERR(mc->base); dev_err(&pdev->dev, "Cannot request MMIO err=%d\n", err); return err; } mc->busclk = devm_clk_get(&pdev->dev, "blkmv"); if (IS_ERR(mc->busclk)) { err = PTR_ERR(mc->busclk); dev_err(&pdev->dev, "Cannot get core clock err=%d\n", err); return err; } mc->irqs = devm_kcalloc(mc->dev, MAXFLOW, sizeof(int), GFP_KERNEL); for (i = 0; i < MAXFLOW; i++) { mc->irqs[i] = platform_get_irq(pdev, i); if (mc->irqs[i] < 0) return mc->irqs[i]; err = devm_request_irq(&pdev->dev, mc->irqs[i], meson_irq_handler, 0, "gxl-crypto", mc); if (err < 0) { dev_err(mc->dev, "Cannot request IRQ for flow %d\n", i); return err; } } err = clk_prepare_enable(mc->busclk); if (err != 0) { dev_err(&pdev->dev, "Cannot prepare_enable busclk\n"); return err; } err = meson_allocate_chanlist(mc); if (err) goto error_flow; err = meson_register_algs(mc); if (err) goto error_alg; #ifdef CONFIG_CRYPTO_DEV_AMLOGIC_GXL_DEBUG mc->dbgfs_dir = debugfs_create_dir("gxl-crypto", NULL); debugfs_create_file("stats", 0444, mc->dbgfs_dir, mc, &meson_debugfs_fops); #endif return 0; error_alg: meson_unregister_algs(mc); error_flow: meson_free_chanlist(mc, MAXFLOW - 1); clk_disable_unprepare(mc->busclk); return err; } static int meson_crypto_remove(struct platform_device *pdev) { struct meson_dev *mc = platform_get_drvdata(pdev); #ifdef CONFIG_CRYPTO_DEV_AMLOGIC_GXL_DEBUG debugfs_remove_recursive(mc->dbgfs_dir); #endif meson_unregister_algs(mc); meson_free_chanlist(mc, MAXFLOW - 1); clk_disable_unprepare(mc->busclk); return 0; } static const struct of_device_id meson_crypto_of_match_table[] = { { .compatible = "amlogic,gxl-crypto", }, {} }; MODULE_DEVICE_TABLE(of, meson_crypto_of_match_table); static struct platform_driver meson_crypto_driver = { .probe = meson_crypto_probe, .remove = meson_crypto_remove, .driver = { .name = "gxl-crypto", .of_match_table = meson_crypto_of_match_table, }, }; module_platform_driver(meson_crypto_driver); MODULE_DESCRIPTION("Amlogic GXL cryptographic offloader"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Corentin Labbe ");