root/drivers/crypto/caam/caamprng.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Driver to expose SEC4 PRNG via crypto RNG API
 *
 * Copyright 2022 NXP
 *
 */

#include <linux/completion.h>
#include <crypto/internal/rng.h>
#include <linux/dma-mapping.h>
#include <linux/kernel.h>
#include "compat.h"
#include "regs.h"
#include "intern.h"
#include "desc_constr.h"
#include "jr.h"
#include "error.h"

/*
 * Length of used descriptors, see caam_init_desc()
 */
#define CAAM_PRNG_MAX_DESC_LEN (CAAM_CMD_SZ +                           \
                            CAAM_CMD_SZ +                               \
                            CAAM_CMD_SZ + CAAM_PTR_SZ_MAX)

/* prng per-device context */
struct caam_prng_ctx {
        int err;
        struct completion done;
};

struct caam_prng_alg {
        struct rng_alg rng;
        bool registered;
};

static void caam_prng_done(struct device *jrdev, u32 *desc, u32 err,
                          void *context)
{
        struct caam_prng_ctx *jctx = context;

        jctx->err = err ? caam_jr_strstatus(jrdev, err) : 0;

        complete(&jctx->done);
}

static u32 *caam_init_reseed_desc(u32 *desc)
{
        init_job_desc(desc, 0); /* + 1 cmd_sz */
        /* Generate random bytes: + 1 cmd_sz */
        append_operation(desc, OP_TYPE_CLASS1_ALG | OP_ALG_ALGSEL_RNG |
                        OP_ALG_AS_FINALIZE);

        print_hex_dump_debug("prng reseed desc@: ", DUMP_PREFIX_ADDRESS,
                             16, 4, desc, desc_bytes(desc), 1);

        return desc;
}

static u32 *caam_init_prng_desc(u32 *desc, dma_addr_t dst_dma, u32 len)
{
        init_job_desc(desc, 0); /* + 1 cmd_sz */
        /* Generate random bytes: + 1 cmd_sz */
        append_operation(desc, OP_ALG_ALGSEL_RNG | OP_TYPE_CLASS1_ALG);
        /* Store bytes: + 1 cmd_sz + caam_ptr_sz  */
        append_fifo_store(desc, dst_dma,
                          len, FIFOST_TYPE_RNGSTORE);

        print_hex_dump_debug("prng job desc@: ", DUMP_PREFIX_ADDRESS,
                             16, 4, desc, desc_bytes(desc), 1);

        return desc;
}

static int caam_prng_generate(struct crypto_rng *tfm,
                             const u8 *src, unsigned int slen,
                             u8 *dst, unsigned int dlen)
{
        unsigned int aligned_dlen = ALIGN(dlen, dma_get_cache_alignment());
        struct caam_prng_ctx ctx;
        struct device *jrdev;
        dma_addr_t dst_dma;
        u32 *desc;
        u8 *buf;
        int ret;

        if (aligned_dlen < dlen)
                return -EOVERFLOW;

        buf = kzalloc(aligned_dlen, GFP_KERNEL);
        if (!buf)
                return -ENOMEM;

        jrdev = caam_jr_alloc();
        ret = PTR_ERR_OR_ZERO(jrdev);
        if (ret) {
                pr_err("Job Ring Device allocation failed\n");
                kfree(buf);
                return ret;
        }

        desc = kzalloc(CAAM_PRNG_MAX_DESC_LEN, GFP_KERNEL);
        if (!desc) {
                ret = -ENOMEM;
                goto out1;
        }

        dst_dma = dma_map_single(jrdev, buf, dlen, DMA_FROM_DEVICE);
        if (dma_mapping_error(jrdev, dst_dma)) {
                dev_err(jrdev, "Failed to map destination buffer memory\n");
                ret = -ENOMEM;
                goto out;
        }

        init_completion(&ctx.done);
        ret = caam_jr_enqueue(jrdev,
                              caam_init_prng_desc(desc, dst_dma, dlen),
                              caam_prng_done, &ctx);

        if (ret == -EINPROGRESS) {
                wait_for_completion(&ctx.done);
                ret = ctx.err;
        }

        dma_unmap_single(jrdev, dst_dma, dlen, DMA_FROM_DEVICE);

        if (!ret)
                memcpy(dst, buf, dlen);
out:
        kfree(desc);
out1:
        caam_jr_free(jrdev);
        kfree(buf);
        return ret;
}

static void caam_prng_exit(struct crypto_tfm *tfm) {}

static int caam_prng_init(struct crypto_tfm *tfm)
{
        return 0;
}

static int caam_prng_seed(struct crypto_rng *tfm,
                         const u8 *seed, unsigned int slen)
{
        struct caam_prng_ctx ctx;
        struct device *jrdev;
        u32 *desc;
        int ret;

        if (slen) {
                pr_err("Seed length should be zero\n");
                return -EINVAL;
        }

        jrdev = caam_jr_alloc();
        ret = PTR_ERR_OR_ZERO(jrdev);
        if (ret) {
                pr_err("Job Ring Device allocation failed\n");
                return ret;
        }

        desc = kzalloc(CAAM_PRNG_MAX_DESC_LEN, GFP_KERNEL);
        if (!desc) {
                caam_jr_free(jrdev);
                return -ENOMEM;
        }

        init_completion(&ctx.done);
        ret = caam_jr_enqueue(jrdev,
                              caam_init_reseed_desc(desc),
                              caam_prng_done, &ctx);

        if (ret == -EINPROGRESS) {
                wait_for_completion(&ctx.done);
                ret = ctx.err;
        }

        kfree(desc);
        caam_jr_free(jrdev);
        return ret;
}

static struct caam_prng_alg caam_prng_alg = {
        .rng = {
                .generate = caam_prng_generate,
                .seed = caam_prng_seed,
                .seedsize = 0,
                .base = {
                        .cra_name = "stdrng",
                        .cra_driver_name = "prng-caam",
                        .cra_priority = 500,
                        .cra_ctxsize = sizeof(struct caam_prng_ctx),
                        .cra_module = THIS_MODULE,
                        .cra_init = caam_prng_init,
                        .cra_exit = caam_prng_exit,
                },
        }
};

void caam_prng_unregister(void *data)
{
        if (caam_prng_alg.registered)
                crypto_unregister_rng(&caam_prng_alg.rng);
}

int caam_prng_register(struct device *ctrldev)
{
        struct caam_drv_private *priv = dev_get_drvdata(ctrldev);
        u32 rng_inst;
        int ret = 0;

        /* Check for available RNG blocks before registration */
        if (priv->era < 10)
                rng_inst = (rd_reg32(&priv->jr[0]->perfmon.cha_num_ls) &
                            CHA_ID_LS_RNG_MASK) >> CHA_ID_LS_RNG_SHIFT;
        else
                rng_inst = rd_reg32(&priv->jr[0]->vreg.rng) & CHA_VER_NUM_MASK;

        if (!rng_inst) {
                dev_dbg(ctrldev, "RNG block is not available... skipping registering algorithm\n");
                return ret;
        }

        ret = crypto_register_rng(&caam_prng_alg.rng);
        if (ret) {
                dev_err(ctrldev,
                        "couldn't register rng crypto alg: %d\n",
                        ret);
                return ret;
        }

        caam_prng_alg.registered = true;

        dev_info(ctrldev,
                 "rng crypto API alg registered %s\n", caam_prng_alg.rng.base.cra_driver_name);

        return 0;
}