root/drivers/crypto/starfive/jh7110-aes.c
// SPDX-License-Identifier: GPL-2.0
/*
 * StarFive AES acceleration driver
 *
 * Copyright (c) 2022 StarFive Technology
 */

#include <crypto/engine.h>
#include <crypto/gcm.h>
#include <crypto/internal/aead.h>
#include <crypto/internal/skcipher.h>
#include <crypto/scatterwalk.h>
#include "jh7110-cryp.h"
#include <linux/err.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/string.h>

#define STARFIVE_AES_REGS_OFFSET        0x100
#define STARFIVE_AES_AESDIO0R           (STARFIVE_AES_REGS_OFFSET + 0x0)
#define STARFIVE_AES_KEY0               (STARFIVE_AES_REGS_OFFSET + 0x4)
#define STARFIVE_AES_KEY1               (STARFIVE_AES_REGS_OFFSET + 0x8)
#define STARFIVE_AES_KEY2               (STARFIVE_AES_REGS_OFFSET + 0xC)
#define STARFIVE_AES_KEY3               (STARFIVE_AES_REGS_OFFSET + 0x10)
#define STARFIVE_AES_KEY4               (STARFIVE_AES_REGS_OFFSET + 0x14)
#define STARFIVE_AES_KEY5               (STARFIVE_AES_REGS_OFFSET + 0x18)
#define STARFIVE_AES_KEY6               (STARFIVE_AES_REGS_OFFSET + 0x1C)
#define STARFIVE_AES_KEY7               (STARFIVE_AES_REGS_OFFSET + 0x20)
#define STARFIVE_AES_CSR                (STARFIVE_AES_REGS_OFFSET + 0x24)
#define STARFIVE_AES_IV0                (STARFIVE_AES_REGS_OFFSET + 0x28)
#define STARFIVE_AES_IV1                (STARFIVE_AES_REGS_OFFSET + 0x2C)
#define STARFIVE_AES_IV2                (STARFIVE_AES_REGS_OFFSET + 0x30)
#define STARFIVE_AES_IV3                (STARFIVE_AES_REGS_OFFSET + 0x34)
#define STARFIVE_AES_NONCE0             (STARFIVE_AES_REGS_OFFSET + 0x3C)
#define STARFIVE_AES_NONCE1             (STARFIVE_AES_REGS_OFFSET + 0x40)
#define STARFIVE_AES_NONCE2             (STARFIVE_AES_REGS_OFFSET + 0x44)
#define STARFIVE_AES_NONCE3             (STARFIVE_AES_REGS_OFFSET + 0x48)
#define STARFIVE_AES_ALEN0              (STARFIVE_AES_REGS_OFFSET + 0x4C)
#define STARFIVE_AES_ALEN1              (STARFIVE_AES_REGS_OFFSET + 0x50)
#define STARFIVE_AES_MLEN0              (STARFIVE_AES_REGS_OFFSET + 0x54)
#define STARFIVE_AES_MLEN1              (STARFIVE_AES_REGS_OFFSET + 0x58)
#define STARFIVE_AES_IVLEN              (STARFIVE_AES_REGS_OFFSET + 0x5C)

#define FLG_MODE_MASK                   GENMASK(2, 0)
#define FLG_ENCRYPT                     BIT(4)

/* Misc */
#define CCM_B0_ADATA                    0x40
#define AES_BLOCK_32                    (AES_BLOCK_SIZE / sizeof(u32))

static inline int starfive_aes_wait_busy(struct starfive_cryp_dev *cryp)
{
        u32 status;

        return readl_relaxed_poll_timeout(cryp->base + STARFIVE_AES_CSR, status,
                                          !(status & STARFIVE_AES_BUSY), 10, 100000);
}

static inline int starfive_aes_wait_keydone(struct starfive_cryp_dev *cryp)
{
        u32 status;

        return readl_relaxed_poll_timeout(cryp->base + STARFIVE_AES_CSR, status,
                                          (status & STARFIVE_AES_KEY_DONE), 10, 100000);
}

static inline int starfive_aes_wait_gcmdone(struct starfive_cryp_dev *cryp)
{
        u32 status;

        return readl_relaxed_poll_timeout(cryp->base + STARFIVE_AES_CSR, status,
                                          (status & STARFIVE_AES_GCM_DONE), 10, 100000);
}

static inline int is_gcm(struct starfive_cryp_dev *cryp)
{
        return (cryp->flags & FLG_MODE_MASK) == STARFIVE_AES_MODE_GCM;
}

static inline bool is_encrypt(struct starfive_cryp_dev *cryp)
{
        return cryp->flags & FLG_ENCRYPT;
}

static void starfive_aes_aead_hw_start(struct starfive_cryp_ctx *ctx, u32 hw_mode)
{
        struct starfive_cryp_dev *cryp = ctx->cryp;
        unsigned int value;

        switch (hw_mode) {
        case STARFIVE_AES_MODE_GCM:
                value = readl(ctx->cryp->base + STARFIVE_AES_CSR);
                value |= STARFIVE_AES_GCM_START;
                writel(value, cryp->base + STARFIVE_AES_CSR);
                starfive_aes_wait_gcmdone(cryp);
                break;
        case STARFIVE_AES_MODE_CCM:
                value = readl(ctx->cryp->base + STARFIVE_AES_CSR);
                value |= STARFIVE_AES_CCM_START;
                writel(value, cryp->base + STARFIVE_AES_CSR);
                break;
        }
}

static inline void starfive_aes_set_alen(struct starfive_cryp_ctx *ctx)
{
        struct starfive_cryp_dev *cryp = ctx->cryp;

        writel(upper_32_bits(cryp->assoclen), cryp->base + STARFIVE_AES_ALEN0);
        writel(lower_32_bits(cryp->assoclen), cryp->base + STARFIVE_AES_ALEN1);
}

static inline void starfive_aes_set_mlen(struct starfive_cryp_ctx *ctx)
{
        struct starfive_cryp_dev *cryp = ctx->cryp;

        writel(upper_32_bits(cryp->total_in), cryp->base + STARFIVE_AES_MLEN0);
        writel(lower_32_bits(cryp->total_in), cryp->base + STARFIVE_AES_MLEN1);
}

static inline int starfive_aes_ccm_check_iv(const u8 *iv)
{
        /* 2 <= L <= 8, so 1 <= L' <= 7. */
        if (iv[0] < 1 || iv[0] > 7)
                return -EINVAL;

        return 0;
}

static int starfive_aes_write_iv(struct starfive_cryp_ctx *ctx, u32 *iv)
{
        struct starfive_cryp_dev *cryp = ctx->cryp;

        writel(iv[0], cryp->base + STARFIVE_AES_IV0);
        writel(iv[1], cryp->base + STARFIVE_AES_IV1);
        writel(iv[2], cryp->base + STARFIVE_AES_IV2);

        if (is_gcm(cryp)) {
                if (starfive_aes_wait_gcmdone(cryp))
                        return -ETIMEDOUT;

                return 0;
        }

        writel(iv[3], cryp->base + STARFIVE_AES_IV3);

        return 0;
}

static inline void starfive_aes_get_iv(struct starfive_cryp_dev *cryp, u32 *iv)
{
        iv[0] = readl(cryp->base + STARFIVE_AES_IV0);
        iv[1] = readl(cryp->base + STARFIVE_AES_IV1);
        iv[2] = readl(cryp->base + STARFIVE_AES_IV2);
        iv[3] = readl(cryp->base + STARFIVE_AES_IV3);
}

static inline void starfive_aes_write_nonce(struct starfive_cryp_ctx *ctx, u32 *nonce)
{
        struct starfive_cryp_dev *cryp = ctx->cryp;

        writel(nonce[0], cryp->base + STARFIVE_AES_NONCE0);
        writel(nonce[1], cryp->base + STARFIVE_AES_NONCE1);
        writel(nonce[2], cryp->base + STARFIVE_AES_NONCE2);
        writel(nonce[3], cryp->base + STARFIVE_AES_NONCE3);
}

static int starfive_aes_write_key(struct starfive_cryp_ctx *ctx)
{
        struct starfive_cryp_dev *cryp = ctx->cryp;
        u32 *key = (u32 *)ctx->key;

        if (ctx->keylen >= AES_KEYSIZE_128) {
                writel(key[0], cryp->base + STARFIVE_AES_KEY0);
                writel(key[1], cryp->base + STARFIVE_AES_KEY1);
                writel(key[2], cryp->base + STARFIVE_AES_KEY2);
                writel(key[3], cryp->base + STARFIVE_AES_KEY3);
        }

        if (ctx->keylen >= AES_KEYSIZE_192) {
                writel(key[4], cryp->base + STARFIVE_AES_KEY4);
                writel(key[5], cryp->base + STARFIVE_AES_KEY5);
        }

        if (ctx->keylen >= AES_KEYSIZE_256) {
                writel(key[6], cryp->base + STARFIVE_AES_KEY6);
                writel(key[7], cryp->base + STARFIVE_AES_KEY7);
        }

        if (starfive_aes_wait_keydone(cryp))
                return -ETIMEDOUT;

        return 0;
}

static int starfive_aes_ccm_init(struct starfive_cryp_ctx *ctx)
{
        struct starfive_cryp_dev *cryp = ctx->cryp;
        u8 iv[AES_BLOCK_SIZE], b0[AES_BLOCK_SIZE];
        unsigned int textlen;

        memcpy(iv, cryp->req.areq->iv, AES_BLOCK_SIZE);
        memset(iv + AES_BLOCK_SIZE - 1 - iv[0], 0, iv[0] + 1);

        /* Build B0 */
        memcpy(b0, iv, AES_BLOCK_SIZE);

        b0[0] |= (8 * ((cryp->authsize - 2) / 2));

        if (cryp->assoclen)
                b0[0] |= CCM_B0_ADATA;

        textlen = cryp->total_in;

        b0[AES_BLOCK_SIZE - 2] = textlen >> 8;
        b0[AES_BLOCK_SIZE - 1] = textlen & 0xFF;

        starfive_aes_write_nonce(ctx, (u32 *)b0);

        return 0;
}

static int starfive_aes_hw_init(struct starfive_cryp_ctx *ctx)
{
        struct starfive_cryp_request_ctx *rctx = ctx->rctx;
        struct starfive_cryp_dev *cryp = ctx->cryp;
        u32 hw_mode;

        /* reset */
        rctx->csr.aes.v = 0;
        rctx->csr.aes.aesrst = 1;
        writel(rctx->csr.aes.v, cryp->base + STARFIVE_AES_CSR);

        /* csr setup */
        hw_mode = cryp->flags & FLG_MODE_MASK;

        rctx->csr.aes.v = 0;

        switch (ctx->keylen) {
        case AES_KEYSIZE_128:
                rctx->csr.aes.keymode = STARFIVE_AES_KEYMODE_128;
                break;
        case AES_KEYSIZE_192:
                rctx->csr.aes.keymode = STARFIVE_AES_KEYMODE_192;
                break;
        case AES_KEYSIZE_256:
                rctx->csr.aes.keymode = STARFIVE_AES_KEYMODE_256;
                break;
        }

        rctx->csr.aes.mode  = hw_mode;
        rctx->csr.aes.cmode = !is_encrypt(cryp);
        rctx->csr.aes.stmode = STARFIVE_AES_MODE_XFB_1;

        if (cryp->side_chan) {
                rctx->csr.aes.delay_aes = 1;
                rctx->csr.aes.vaes_start = 1;
        }

        writel(rctx->csr.aes.v, cryp->base + STARFIVE_AES_CSR);

        cryp->err = starfive_aes_write_key(ctx);
        if (cryp->err)
                return cryp->err;

        switch (hw_mode) {
        case STARFIVE_AES_MODE_GCM:
                starfive_aes_set_alen(ctx);
                starfive_aes_set_mlen(ctx);
                writel(GCM_AES_IV_SIZE, cryp->base + STARFIVE_AES_IVLEN);
                starfive_aes_aead_hw_start(ctx, hw_mode);
                starfive_aes_write_iv(ctx, (void *)cryp->req.areq->iv);
                break;
        case STARFIVE_AES_MODE_CCM:
                starfive_aes_set_alen(ctx);
                starfive_aes_set_mlen(ctx);
                starfive_aes_ccm_init(ctx);
                starfive_aes_aead_hw_start(ctx, hw_mode);
                break;
        case STARFIVE_AES_MODE_CBC:
        case STARFIVE_AES_MODE_CTR:
                starfive_aes_write_iv(ctx, (void *)cryp->req.sreq->iv);
                break;
        default:
                break;
        }

        return cryp->err;
}

static int starfive_aes_read_authtag(struct starfive_cryp_ctx *ctx)
{
        struct starfive_cryp_dev *cryp = ctx->cryp;
        struct starfive_cryp_request_ctx *rctx = ctx->rctx;
        int i;

        if (starfive_aes_wait_busy(cryp))
                return dev_err_probe(cryp->dev, -ETIMEDOUT,
                                     "Timeout waiting for tag generation.");

        if ((cryp->flags & FLG_MODE_MASK) == STARFIVE_AES_MODE_GCM) {
                cryp->tag_out[0] = readl(cryp->base + STARFIVE_AES_NONCE0);
                cryp->tag_out[1] = readl(cryp->base + STARFIVE_AES_NONCE1);
                cryp->tag_out[2] = readl(cryp->base + STARFIVE_AES_NONCE2);
                cryp->tag_out[3] = readl(cryp->base + STARFIVE_AES_NONCE3);
        } else {
                for (i = 0; i < AES_BLOCK_32; i++)
                        cryp->tag_out[i] = readl(cryp->base + STARFIVE_AES_AESDIO0R);
        }

        if (is_encrypt(cryp)) {
                scatterwalk_map_and_copy(cryp->tag_out, rctx->out_sg,
                                         cryp->total_in, cryp->authsize, 1);
        } else {
                if (crypto_memneq(cryp->tag_in, cryp->tag_out, cryp->authsize))
                        return -EBADMSG;
        }

        return 0;
}

static void starfive_aes_finish_req(struct starfive_cryp_ctx *ctx)
{
        struct starfive_cryp_dev *cryp = ctx->cryp;
        int err = cryp->err;

        if (!err && cryp->authsize)
                err = starfive_aes_read_authtag(ctx);

        if (!err && ((cryp->flags & FLG_MODE_MASK) == STARFIVE_AES_MODE_CBC ||
                     (cryp->flags & FLG_MODE_MASK) == STARFIVE_AES_MODE_CTR))
                starfive_aes_get_iv(cryp, (void *)cryp->req.sreq->iv);

        if (cryp->authsize)
                crypto_finalize_aead_request(cryp->engine, cryp->req.areq, err);
        else
                crypto_finalize_skcipher_request(cryp->engine, cryp->req.sreq,
                                                 err);
}

static int starfive_aes_gcm_write_adata(struct starfive_cryp_ctx *ctx)
{
        struct starfive_cryp_dev *cryp = ctx->cryp;
        struct starfive_cryp_request_ctx *rctx = ctx->rctx;
        u32 *buffer;
        int total_len, loop;

        total_len = ALIGN(cryp->assoclen, AES_BLOCK_SIZE) / sizeof(unsigned int);
        buffer = (u32 *)rctx->adata;

        for (loop = 0; loop < total_len; loop += 4) {
                writel(*buffer, cryp->base + STARFIVE_AES_NONCE0);
                buffer++;
                writel(*buffer, cryp->base + STARFIVE_AES_NONCE1);
                buffer++;
                writel(*buffer, cryp->base + STARFIVE_AES_NONCE2);
                buffer++;
                writel(*buffer, cryp->base + STARFIVE_AES_NONCE3);
                buffer++;
        }

        if (starfive_aes_wait_gcmdone(cryp))
                return dev_err_probe(cryp->dev, -ETIMEDOUT,
                                     "Timeout processing gcm aad block");

        return 0;
}

static int starfive_aes_ccm_write_adata(struct starfive_cryp_ctx *ctx)
{
        struct starfive_cryp_dev *cryp = ctx->cryp;
        struct starfive_cryp_request_ctx *rctx = ctx->rctx;
        u32 *buffer;
        u8 *ci;
        int total_len, loop;

        total_len = cryp->assoclen;

        ci = rctx->adata;
        writeb(*ci, cryp->base + STARFIVE_AES_AESDIO0R);
        ci++;
        writeb(*ci, cryp->base + STARFIVE_AES_AESDIO0R);
        ci++;
        total_len -= 2;
        buffer = (u32 *)ci;

        for (loop = 0; loop < 3; loop++, buffer++)
                writel(*buffer, cryp->base + STARFIVE_AES_AESDIO0R);

        total_len -= 12;

        while (total_len > 0) {
                for (loop = 0; loop < AES_BLOCK_32; loop++, buffer++)
                        writel(*buffer, cryp->base + STARFIVE_AES_AESDIO0R);

                total_len -= AES_BLOCK_SIZE;
        }

        if (starfive_aes_wait_busy(cryp))
                return dev_err_probe(cryp->dev, -ETIMEDOUT,
                                     "Timeout processing ccm aad block");

        return 0;
}

static void starfive_aes_dma_done(void *param)
{
        struct starfive_cryp_dev *cryp = param;

        complete(&cryp->dma_done);
}

static void starfive_aes_dma_init(struct starfive_cryp_dev *cryp)
{
        cryp->cfg_in.direction = DMA_MEM_TO_DEV;
        cryp->cfg_in.src_addr_width = DMA_SLAVE_BUSWIDTH_16_BYTES;
        cryp->cfg_in.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
        cryp->cfg_in.src_maxburst = cryp->dma_maxburst;
        cryp->cfg_in.dst_maxburst = cryp->dma_maxburst;
        cryp->cfg_in.dst_addr = cryp->phys_base + STARFIVE_ALG_FIFO_OFFSET;

        dmaengine_slave_config(cryp->tx, &cryp->cfg_in);

        cryp->cfg_out.direction = DMA_DEV_TO_MEM;
        cryp->cfg_out.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
        cryp->cfg_out.dst_addr_width = DMA_SLAVE_BUSWIDTH_16_BYTES;
        cryp->cfg_out.src_maxburst = 4;
        cryp->cfg_out.dst_maxburst = 4;
        cryp->cfg_out.src_addr = cryp->phys_base + STARFIVE_ALG_FIFO_OFFSET;

        dmaengine_slave_config(cryp->rx, &cryp->cfg_out);

        init_completion(&cryp->dma_done);
}

static int starfive_aes_dma_xfer(struct starfive_cryp_dev *cryp,
                                 struct scatterlist *src,
                                 struct scatterlist *dst,
                                 int len)
{
        struct dma_async_tx_descriptor *in_desc, *out_desc;
        union starfive_alg_cr alg_cr;
        int ret = 0, in_save, out_save;

        alg_cr.v = 0;
        alg_cr.start = 1;
        alg_cr.aes_dma_en = 1;
        writel(alg_cr.v, cryp->base + STARFIVE_ALG_CR_OFFSET);

        in_save = sg_dma_len(src);
        out_save = sg_dma_len(dst);

        writel(ALIGN(len, AES_BLOCK_SIZE), cryp->base + STARFIVE_DMA_IN_LEN_OFFSET);
        writel(ALIGN(len, AES_BLOCK_SIZE), cryp->base + STARFIVE_DMA_OUT_LEN_OFFSET);

        sg_dma_len(src) = ALIGN(len, AES_BLOCK_SIZE);
        sg_dma_len(dst) = ALIGN(len, AES_BLOCK_SIZE);

        out_desc = dmaengine_prep_slave_sg(cryp->rx, dst, 1, DMA_DEV_TO_MEM,
                                           DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
        if (!out_desc) {
                ret = -EINVAL;
                goto dma_err;
        }

        out_desc->callback = starfive_aes_dma_done;
        out_desc->callback_param = cryp;

        reinit_completion(&cryp->dma_done);
        dmaengine_submit(out_desc);
        dma_async_issue_pending(cryp->rx);

        in_desc = dmaengine_prep_slave_sg(cryp->tx, src, 1, DMA_MEM_TO_DEV,
                                          DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
        if (!in_desc) {
                ret = -EINVAL;
                goto dma_err;
        }

        dmaengine_submit(in_desc);
        dma_async_issue_pending(cryp->tx);

        if (!wait_for_completion_timeout(&cryp->dma_done,
                                         msecs_to_jiffies(1000)))
                ret = -ETIMEDOUT;

dma_err:
        sg_dma_len(src) = in_save;
        sg_dma_len(dst) = out_save;

        alg_cr.v = 0;
        alg_cr.clear = 1;
        writel(alg_cr.v, cryp->base + STARFIVE_ALG_CR_OFFSET);

        return ret;
}

static int starfive_aes_map_sg(struct starfive_cryp_dev *cryp,
                               struct scatterlist *src,
                               struct scatterlist *dst)
{
        struct scatterlist *stsg, *dtsg;
        struct scatterlist _src[2], _dst[2];
        unsigned int remain = cryp->total_in;
        unsigned int len, src_nents, dst_nents;
        int ret;

        if (src == dst) {
                for (stsg = src, dtsg = dst; remain > 0;
                     stsg = sg_next(stsg), dtsg = sg_next(dtsg)) {
                        src_nents = dma_map_sg(cryp->dev, stsg, 1, DMA_BIDIRECTIONAL);
                        if (src_nents == 0)
                                return -ENOMEM;

                        dst_nents = src_nents;
                        len = min(sg_dma_len(stsg), remain);

                        ret = starfive_aes_dma_xfer(cryp, stsg, dtsg, len);
                        dma_unmap_sg(cryp->dev, stsg, 1, DMA_BIDIRECTIONAL);
                        if (ret)
                                return ret;

                        remain -= len;
                }
        } else {
                for (stsg = src, dtsg = dst;;) {
                        src_nents = dma_map_sg(cryp->dev, stsg, 1, DMA_TO_DEVICE);
                        if (src_nents == 0)
                                return -ENOMEM;

                        dst_nents = dma_map_sg(cryp->dev, dtsg, 1, DMA_FROM_DEVICE);
                        if (dst_nents == 0)
                                return -ENOMEM;

                        len = min(sg_dma_len(stsg), sg_dma_len(dtsg));
                        len = min(len, remain);

                        ret = starfive_aes_dma_xfer(cryp, stsg, dtsg, len);
                        dma_unmap_sg(cryp->dev, stsg, 1, DMA_TO_DEVICE);
                        dma_unmap_sg(cryp->dev, dtsg, 1, DMA_FROM_DEVICE);
                        if (ret)
                                return ret;

                        remain -= len;
                        if (remain == 0)
                                break;

                        if (sg_dma_len(stsg) - len) {
                                stsg = scatterwalk_ffwd(_src, stsg, len);
                                dtsg = sg_next(dtsg);
                        } else if (sg_dma_len(dtsg) - len) {
                                dtsg = scatterwalk_ffwd(_dst, dtsg, len);
                                stsg = sg_next(stsg);
                        } else {
                                stsg = sg_next(stsg);
                                dtsg = sg_next(dtsg);
                        }
                }
        }

        return 0;
}

static int starfive_aes_do_one_req(struct crypto_engine *engine, void *areq)
{
        struct skcipher_request *req =
                container_of(areq, struct skcipher_request, base);
        struct starfive_cryp_ctx *ctx =
                crypto_skcipher_ctx(crypto_skcipher_reqtfm(req));
        struct starfive_cryp_request_ctx *rctx = skcipher_request_ctx(req);
        struct starfive_cryp_dev *cryp = ctx->cryp;
        int ret;

        cryp->req.sreq = req;
        cryp->total_in = req->cryptlen;
        cryp->total_out = req->cryptlen;
        cryp->assoclen = 0;
        cryp->authsize = 0;

        rctx->in_sg = req->src;
        rctx->out_sg = req->dst;

        ctx->rctx = rctx;

        ret = starfive_aes_hw_init(ctx);
        if (ret)
                return ret;

        if (!cryp->total_in)
                goto finish_req;

        starfive_aes_dma_init(cryp);

        ret = starfive_aes_map_sg(cryp, rctx->in_sg, rctx->out_sg);
        if (ret)
                return ret;

finish_req:
        starfive_aes_finish_req(ctx);

        return 0;
}

static int starfive_aes_init_tfm(struct crypto_skcipher *tfm,
                                 const char *alg_name)
{
        struct starfive_cryp_ctx *ctx = crypto_skcipher_ctx(tfm);

        ctx->cryp = starfive_cryp_find_dev(ctx);
        if (!ctx->cryp)
                return -ENODEV;

        ctx->skcipher_fbk = crypto_alloc_skcipher(alg_name, 0,
                                                  CRYPTO_ALG_NEED_FALLBACK);
        if (IS_ERR(ctx->skcipher_fbk))
                return dev_err_probe(ctx->cryp->dev, PTR_ERR(ctx->skcipher_fbk),
                                     "%s() failed to allocate fallback for %s\n",
                                     __func__, alg_name);

        crypto_skcipher_set_reqsize(tfm, sizeof(struct starfive_cryp_request_ctx) +
                                    crypto_skcipher_reqsize(ctx->skcipher_fbk));

        return 0;
}

static void starfive_aes_exit_tfm(struct crypto_skcipher *tfm)
{
        struct starfive_cryp_ctx *ctx = crypto_skcipher_ctx(tfm);

        crypto_free_skcipher(ctx->skcipher_fbk);
}

static int starfive_aes_aead_do_one_req(struct crypto_engine *engine, void *areq)
{
        struct aead_request *req =
                container_of(areq, struct aead_request, base);
        struct starfive_cryp_ctx *ctx =
                crypto_aead_ctx(crypto_aead_reqtfm(req));
        struct starfive_cryp_dev *cryp = ctx->cryp;
        struct starfive_cryp_request_ctx *rctx = aead_request_ctx(req);
        struct scatterlist _src[2], _dst[2];
        int ret;

        cryp->req.areq = req;
        cryp->assoclen = req->assoclen;
        cryp->authsize = crypto_aead_authsize(crypto_aead_reqtfm(req));

        rctx->in_sg = scatterwalk_ffwd(_src, req->src, cryp->assoclen);
        if (req->src == req->dst)
                rctx->out_sg = rctx->in_sg;
        else
                rctx->out_sg = scatterwalk_ffwd(_dst, req->dst, cryp->assoclen);

        if (is_encrypt(cryp)) {
                cryp->total_in = req->cryptlen;
                cryp->total_out = req->cryptlen;
        } else {
                cryp->total_in = req->cryptlen - cryp->authsize;
                cryp->total_out = cryp->total_in;
                scatterwalk_map_and_copy(cryp->tag_in, req->src,
                                         cryp->total_in + cryp->assoclen,
                                         cryp->authsize, 0);
        }

        if (cryp->assoclen) {
                rctx->adata = kzalloc(cryp->assoclen + AES_BLOCK_SIZE, GFP_KERNEL);
                if (!rctx->adata)
                        return -ENOMEM;

                if (sg_copy_to_buffer(req->src, sg_nents_for_len(req->src, cryp->assoclen),
                                      rctx->adata, cryp->assoclen) != cryp->assoclen) {
                        kfree(rctx->adata);
                        return -EINVAL;
                }
        }

        if (cryp->total_in)
                sg_zero_buffer(rctx->in_sg, sg_nents(rctx->in_sg),
                               sg_dma_len(rctx->in_sg) - cryp->total_in,
                               cryp->total_in);

        ctx->rctx = rctx;

        ret = starfive_aes_hw_init(ctx);
        if (ret) {
                if (cryp->assoclen)
                        kfree(rctx->adata);
                return ret;
        }

        if (!cryp->assoclen)
                goto write_text;

        if ((cryp->flags & FLG_MODE_MASK) == STARFIVE_AES_MODE_CCM)
                ret = starfive_aes_ccm_write_adata(ctx);
        else
                ret = starfive_aes_gcm_write_adata(ctx);

        kfree(rctx->adata);

        if (ret)
                return ret;

write_text:
        if (!cryp->total_in)
                goto finish_req;

        starfive_aes_dma_init(cryp);

        ret = starfive_aes_map_sg(cryp, rctx->in_sg, rctx->out_sg);
        if (ret)
                return ret;

finish_req:
        starfive_aes_finish_req(ctx);
        return 0;
}

static int starfive_aes_aead_init_tfm(struct crypto_aead *tfm,
                                      const char *alg_name)
{
        struct starfive_cryp_ctx *ctx = crypto_aead_ctx(tfm);

        ctx->cryp = starfive_cryp_find_dev(ctx);
        if (!ctx->cryp)
                return -ENODEV;

        ctx->aead_fbk = crypto_alloc_aead(alg_name, 0,
                                          CRYPTO_ALG_NEED_FALLBACK);
        if (IS_ERR(ctx->aead_fbk))
                return dev_err_probe(ctx->cryp->dev, PTR_ERR(ctx->aead_fbk),
                                     "%s() failed to allocate fallback for %s\n",
                                     __func__, alg_name);

        crypto_aead_set_reqsize(tfm, sizeof(struct starfive_cryp_request_ctx) +
                                crypto_aead_reqsize(ctx->aead_fbk));

        return 0;
}

static void starfive_aes_aead_exit_tfm(struct crypto_aead *tfm)
{
        struct starfive_cryp_ctx *ctx = crypto_aead_ctx(tfm);

        crypto_free_aead(ctx->aead_fbk);
}

static bool starfive_aes_check_unaligned(struct starfive_cryp_dev *cryp,
                                         struct scatterlist *src,
                                         struct scatterlist *dst)
{
        struct scatterlist *tsg;
        int i;

        for_each_sg(src, tsg, sg_nents(src), i)
                if (!IS_ALIGNED(tsg->offset, sizeof(u32)) ||
                    (!IS_ALIGNED(tsg->length, AES_BLOCK_SIZE) &&
                     !sg_is_last(tsg)))
                        return true;

        if (src != dst)
                for_each_sg(dst, tsg, sg_nents(dst), i)
                        if (!IS_ALIGNED(tsg->offset, sizeof(u32)) ||
                            (!IS_ALIGNED(tsg->length, AES_BLOCK_SIZE) &&
                             !sg_is_last(tsg)))
                                return true;

        return false;
}

static int starfive_aes_do_fallback(struct skcipher_request *req, bool enc)
{
        struct starfive_cryp_ctx *ctx =
                crypto_skcipher_ctx(crypto_skcipher_reqtfm(req));
        struct skcipher_request *subreq = skcipher_request_ctx(req);

        skcipher_request_set_tfm(subreq, ctx->skcipher_fbk);
        skcipher_request_set_callback(subreq, req->base.flags,
                                      req->base.complete,
                                      req->base.data);
        skcipher_request_set_crypt(subreq, req->src, req->dst,
                                   req->cryptlen, req->iv);

        return enc ? crypto_skcipher_encrypt(subreq) :
                     crypto_skcipher_decrypt(subreq);
}

static int starfive_aes_crypt(struct skcipher_request *req, unsigned long flags)
{
        struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
        struct starfive_cryp_ctx *ctx = crypto_skcipher_ctx(tfm);
        struct starfive_cryp_dev *cryp = ctx->cryp;
        unsigned int blocksize_align = crypto_skcipher_blocksize(tfm) - 1;

        cryp->flags = flags;

        if ((cryp->flags & FLG_MODE_MASK) == STARFIVE_AES_MODE_ECB ||
            (cryp->flags & FLG_MODE_MASK) == STARFIVE_AES_MODE_CBC)
                if (req->cryptlen & blocksize_align)
                        return -EINVAL;

        if (starfive_aes_check_unaligned(cryp, req->src, req->dst))
                return starfive_aes_do_fallback(req, is_encrypt(cryp));

        return crypto_transfer_skcipher_request_to_engine(cryp->engine, req);
}

static int starfive_aes_aead_do_fallback(struct aead_request *req, bool enc)
{
        struct starfive_cryp_ctx *ctx =
                crypto_aead_ctx(crypto_aead_reqtfm(req));
        struct aead_request *subreq = aead_request_ctx(req);

        aead_request_set_tfm(subreq, ctx->aead_fbk);
        aead_request_set_callback(subreq, req->base.flags,
                                  req->base.complete,
                                  req->base.data);
        aead_request_set_crypt(subreq, req->src, req->dst,
                               req->cryptlen, req->iv);
        aead_request_set_ad(subreq, req->assoclen);

        return enc ? crypto_aead_encrypt(subreq) :
                     crypto_aead_decrypt(subreq);
}

static int starfive_aes_aead_crypt(struct aead_request *req, unsigned long flags)
{
        struct starfive_cryp_ctx *ctx = crypto_aead_ctx(crypto_aead_reqtfm(req));
        struct starfive_cryp_dev *cryp = ctx->cryp;
        struct scatterlist *src, *dst, _src[2], _dst[2];

        cryp->flags = flags;

        /* aes-ccm does not support tag verification for non-aligned text,
         * use fallback for ccm decryption instead.
         */
        if (((cryp->flags & FLG_MODE_MASK) == STARFIVE_AES_MODE_CCM) &&
            !is_encrypt(cryp))
                return starfive_aes_aead_do_fallback(req, 0);

        src = scatterwalk_ffwd(_src, req->src, req->assoclen);

        if (req->src == req->dst)
                dst = src;
        else
                dst = scatterwalk_ffwd(_dst, req->dst, req->assoclen);

        if (starfive_aes_check_unaligned(cryp, src, dst))
                return starfive_aes_aead_do_fallback(req, is_encrypt(cryp));

        return crypto_transfer_aead_request_to_engine(cryp->engine, req);
}

static int starfive_aes_setkey(struct crypto_skcipher *tfm, const u8 *key,
                               unsigned int keylen)
{
        struct starfive_cryp_ctx *ctx = crypto_skcipher_ctx(tfm);

        if (!key || !keylen)
                return -EINVAL;

        if (keylen != AES_KEYSIZE_128 &&
            keylen != AES_KEYSIZE_192 &&
            keylen != AES_KEYSIZE_256)
                return -EINVAL;

        memcpy(ctx->key, key, keylen);
        ctx->keylen = keylen;

        return crypto_skcipher_setkey(ctx->skcipher_fbk, key, keylen);
}

static int starfive_aes_aead_setkey(struct crypto_aead *tfm, const u8 *key,
                                    unsigned int keylen)
{
        struct starfive_cryp_ctx *ctx = crypto_aead_ctx(tfm);

        if (!key || !keylen)
                return -EINVAL;

        if (keylen != AES_KEYSIZE_128 &&
            keylen != AES_KEYSIZE_192 &&
            keylen != AES_KEYSIZE_256)
                return -EINVAL;

        memcpy(ctx->key, key, keylen);
        ctx->keylen = keylen;

        return crypto_aead_setkey(ctx->aead_fbk, key, keylen);
}

static int starfive_aes_gcm_setauthsize(struct crypto_aead *tfm,
                                        unsigned int authsize)
{
        struct starfive_cryp_ctx *ctx = crypto_aead_ctx(tfm);
        int ret;

        ret = crypto_gcm_check_authsize(authsize);
        if (ret)
                return ret;

        return crypto_aead_setauthsize(ctx->aead_fbk, authsize);
}

static int starfive_aes_ccm_setauthsize(struct crypto_aead *tfm,
                                        unsigned int authsize)
{
        struct starfive_cryp_ctx *ctx = crypto_aead_ctx(tfm);

        switch (authsize) {
        case 4:
        case 6:
        case 8:
        case 10:
        case 12:
        case 14:
        case 16:
                break;
        default:
                return -EINVAL;
        }

        return crypto_aead_setauthsize(ctx->aead_fbk, authsize);
}

static int starfive_aes_ecb_encrypt(struct skcipher_request *req)
{
        return starfive_aes_crypt(req, STARFIVE_AES_MODE_ECB | FLG_ENCRYPT);
}

static int starfive_aes_ecb_decrypt(struct skcipher_request *req)
{
        return starfive_aes_crypt(req, STARFIVE_AES_MODE_ECB);
}

static int starfive_aes_cbc_encrypt(struct skcipher_request *req)
{
        return starfive_aes_crypt(req, STARFIVE_AES_MODE_CBC | FLG_ENCRYPT);
}

static int starfive_aes_cbc_decrypt(struct skcipher_request *req)
{
        return starfive_aes_crypt(req, STARFIVE_AES_MODE_CBC);
}

static int starfive_aes_ctr_encrypt(struct skcipher_request *req)
{
        return starfive_aes_crypt(req, STARFIVE_AES_MODE_CTR | FLG_ENCRYPT);
}

static int starfive_aes_ctr_decrypt(struct skcipher_request *req)
{
        return starfive_aes_crypt(req, STARFIVE_AES_MODE_CTR);
}

static int starfive_aes_gcm_encrypt(struct aead_request *req)
{
        return starfive_aes_aead_crypt(req, STARFIVE_AES_MODE_GCM | FLG_ENCRYPT);
}

static int starfive_aes_gcm_decrypt(struct aead_request *req)
{
        return starfive_aes_aead_crypt(req, STARFIVE_AES_MODE_GCM);
}

static int starfive_aes_ccm_encrypt(struct aead_request *req)
{
        int ret;

        ret = starfive_aes_ccm_check_iv(req->iv);
        if (ret)
                return ret;

        return starfive_aes_aead_crypt(req, STARFIVE_AES_MODE_CCM | FLG_ENCRYPT);
}

static int starfive_aes_ccm_decrypt(struct aead_request *req)
{
        int ret;

        ret = starfive_aes_ccm_check_iv(req->iv);
        if (ret)
                return ret;

        return starfive_aes_aead_crypt(req, STARFIVE_AES_MODE_CCM);
}

static int starfive_aes_ecb_init_tfm(struct crypto_skcipher *tfm)
{
        return starfive_aes_init_tfm(tfm, "ecb(aes-lib)");
}

static int starfive_aes_cbc_init_tfm(struct crypto_skcipher *tfm)
{
        return starfive_aes_init_tfm(tfm, "cbc(aes-lib)");
}

static int starfive_aes_ctr_init_tfm(struct crypto_skcipher *tfm)
{
        return starfive_aes_init_tfm(tfm, "ctr(aes-lib)");
}

static int starfive_aes_ccm_init_tfm(struct crypto_aead *tfm)
{
        return starfive_aes_aead_init_tfm(tfm, "ccm_base(ctr(aes-lib),cbcmac-aes-lib)");
}

static int starfive_aes_gcm_init_tfm(struct crypto_aead *tfm)
{
        return starfive_aes_aead_init_tfm(tfm, "gcm_base(ctr(aes-lib),ghash-lib)");
}

static struct skcipher_engine_alg skcipher_algs[] = {
{
        .base.init                      = starfive_aes_ecb_init_tfm,
        .base.exit                      = starfive_aes_exit_tfm,
        .base.setkey                    = starfive_aes_setkey,
        .base.encrypt                   = starfive_aes_ecb_encrypt,
        .base.decrypt                   = starfive_aes_ecb_decrypt,
        .base.min_keysize               = AES_MIN_KEY_SIZE,
        .base.max_keysize               = AES_MAX_KEY_SIZE,
        .base.base = {
                .cra_name               = "ecb(aes)",
                .cra_driver_name        = "starfive-ecb-aes",
                .cra_priority           = 200,
                .cra_flags              = CRYPTO_ALG_ASYNC |
                                          CRYPTO_ALG_NEED_FALLBACK,
                .cra_blocksize          = AES_BLOCK_SIZE,
                .cra_ctxsize            = sizeof(struct starfive_cryp_ctx),
                .cra_alignmask          = 0xf,
                .cra_module             = THIS_MODULE,
        },
        .op = {
                .do_one_request = starfive_aes_do_one_req,
        },
}, {
        .base.init                      = starfive_aes_cbc_init_tfm,
        .base.exit                      = starfive_aes_exit_tfm,
        .base.setkey                    = starfive_aes_setkey,
        .base.encrypt                   = starfive_aes_cbc_encrypt,
        .base.decrypt                   = starfive_aes_cbc_decrypt,
        .base.min_keysize               = AES_MIN_KEY_SIZE,
        .base.max_keysize               = AES_MAX_KEY_SIZE,
        .base.ivsize                    = AES_BLOCK_SIZE,
        .base.base = {
                .cra_name               = "cbc(aes)",
                .cra_driver_name        = "starfive-cbc-aes",
                .cra_priority           = 200,
                .cra_flags              = CRYPTO_ALG_ASYNC |
                                          CRYPTO_ALG_NEED_FALLBACK,
                .cra_blocksize          = AES_BLOCK_SIZE,
                .cra_ctxsize            = sizeof(struct starfive_cryp_ctx),
                .cra_alignmask          = 0xf,
                .cra_module             = THIS_MODULE,
        },
        .op = {
                .do_one_request = starfive_aes_do_one_req,
        },
}, {
        .base.init                      = starfive_aes_ctr_init_tfm,
        .base.exit                      = starfive_aes_exit_tfm,
        .base.setkey                    = starfive_aes_setkey,
        .base.encrypt                   = starfive_aes_ctr_encrypt,
        .base.decrypt                   = starfive_aes_ctr_decrypt,
        .base.min_keysize               = AES_MIN_KEY_SIZE,
        .base.max_keysize               = AES_MAX_KEY_SIZE,
        .base.ivsize                    = AES_BLOCK_SIZE,
        .base.base = {
                .cra_name               = "ctr(aes)",
                .cra_driver_name        = "starfive-ctr-aes",
                .cra_priority           = 200,
                .cra_flags              = CRYPTO_ALG_ASYNC |
                                          CRYPTO_ALG_NEED_FALLBACK,
                .cra_blocksize          = 1,
                .cra_ctxsize            = sizeof(struct starfive_cryp_ctx),
                .cra_alignmask          = 0xf,
                .cra_module             = THIS_MODULE,
        },
        .op = {
                .do_one_request = starfive_aes_do_one_req,
        },
},
};

static struct aead_engine_alg aead_algs[] = {
{
        .base.setkey                    = starfive_aes_aead_setkey,
        .base.setauthsize               = starfive_aes_gcm_setauthsize,
        .base.encrypt                   = starfive_aes_gcm_encrypt,
        .base.decrypt                   = starfive_aes_gcm_decrypt,
        .base.init                      = starfive_aes_gcm_init_tfm,
        .base.exit                      = starfive_aes_aead_exit_tfm,
        .base.ivsize                    = GCM_AES_IV_SIZE,
        .base.maxauthsize               = AES_BLOCK_SIZE,
        .base.base = {
                .cra_name               = "gcm(aes)",
                .cra_driver_name        = "starfive-gcm-aes",
                .cra_priority           = 200,
                .cra_flags              = CRYPTO_ALG_ASYNC |
                                          CRYPTO_ALG_NEED_FALLBACK,
                .cra_blocksize          = 1,
                .cra_ctxsize            = sizeof(struct starfive_cryp_ctx),
                .cra_alignmask          = 0xf,
                .cra_module             = THIS_MODULE,
        },
        .op = {
                .do_one_request = starfive_aes_aead_do_one_req,
        },
}, {
        .base.setkey                    = starfive_aes_aead_setkey,
        .base.setauthsize               = starfive_aes_ccm_setauthsize,
        .base.encrypt                   = starfive_aes_ccm_encrypt,
        .base.decrypt                   = starfive_aes_ccm_decrypt,
        .base.init                      = starfive_aes_ccm_init_tfm,
        .base.exit                      = starfive_aes_aead_exit_tfm,
        .base.ivsize                    = AES_BLOCK_SIZE,
        .base.maxauthsize               = AES_BLOCK_SIZE,
        .base.base = {
                .cra_name               = "ccm(aes)",
                .cra_driver_name        = "starfive-ccm-aes",
                .cra_priority           = 200,
                .cra_flags              = CRYPTO_ALG_ASYNC |
                                          CRYPTO_ALG_NEED_FALLBACK,
                .cra_blocksize          = 1,
                .cra_ctxsize            = sizeof(struct starfive_cryp_ctx),
                .cra_alignmask          = 0xf,
                .cra_module             = THIS_MODULE,
        },
        .op = {
                .do_one_request = starfive_aes_aead_do_one_req,
        },
},
};

int starfive_aes_register_algs(void)
{
        int ret;

        ret = crypto_engine_register_skciphers(skcipher_algs, ARRAY_SIZE(skcipher_algs));
        if (ret)
                return ret;

        ret = crypto_engine_register_aeads(aead_algs, ARRAY_SIZE(aead_algs));
        if (ret)
                crypto_engine_unregister_skciphers(skcipher_algs, ARRAY_SIZE(skcipher_algs));

        return ret;
}

void starfive_aes_unregister_algs(void)
{
        crypto_engine_unregister_aeads(aead_algs, ARRAY_SIZE(aead_algs));
        crypto_engine_unregister_skciphers(skcipher_algs, ARRAY_SIZE(skcipher_algs));
}