diff options
Diffstat (limited to '')
-rw-r--r-- | drivers/crypto/caam/caamrng.c | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/drivers/crypto/caam/caamrng.c b/drivers/crypto/caam/caamrng.c new file mode 100644 index 000000000..77d048dfe --- /dev/null +++ b/drivers/crypto/caam/caamrng.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * caam - Freescale FSL CAAM support for hw_random + * + * Copyright 2011 Freescale Semiconductor, Inc. + * Copyright 2018-2019 NXP + * + * Based on caamalg.c crypto API driver. + * + */ + +#include <linux/hw_random.h> +#include <linux/completion.h> +#include <linux/atomic.h> +#include <linux/kfifo.h> + +#include "compat.h" + +#include "regs.h" +#include "intern.h" +#include "desc_constr.h" +#include "jr.h" +#include "error.h" + +#define CAAM_RNG_MAX_FIFO_STORE_SIZE 16 + +/* + * Length of used descriptors, see caam_init_desc() + */ +#define CAAM_RNG_DESC_LEN (CAAM_CMD_SZ + \ + CAAM_CMD_SZ + \ + CAAM_CMD_SZ + CAAM_PTR_SZ_MAX) + +/* rng per-device context */ +struct caam_rng_ctx { + struct hwrng rng; + struct device *jrdev; + struct device *ctrldev; + void *desc_async; + void *desc_sync; + struct work_struct worker; + struct kfifo fifo; +}; + +struct caam_rng_job_ctx { + struct completion *done; + int *err; +}; + +static struct caam_rng_ctx *to_caam_rng_ctx(struct hwrng *r) +{ + return (struct caam_rng_ctx *)r->priv; +} + +static void caam_rng_done(struct device *jrdev, u32 *desc, u32 err, + void *context) +{ + struct caam_rng_job_ctx *jctx = context; + + if (err) + *jctx->err = caam_jr_strstatus(jrdev, err); + + complete(jctx->done); +} + +static u32 *caam_init_desc(u32 *desc, dma_addr_t dst_dma) +{ + 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 | + OP_ALG_PR_ON); + /* Store bytes: + 1 cmd_sz + caam_ptr_sz */ + append_fifo_store(desc, dst_dma, + CAAM_RNG_MAX_FIFO_STORE_SIZE, FIFOST_TYPE_RNGSTORE); + + print_hex_dump_debug("rng job desc@: ", DUMP_PREFIX_ADDRESS, + 16, 4, desc, desc_bytes(desc), 1); + + return desc; +} + +static int caam_rng_read_one(struct device *jrdev, + void *dst, int len, + void *desc, + struct completion *done) +{ + dma_addr_t dst_dma; + int err, ret = 0; + struct caam_rng_job_ctx jctx = { + .done = done, + .err = &ret, + }; + + len = CAAM_RNG_MAX_FIFO_STORE_SIZE; + + dst_dma = dma_map_single(jrdev, dst, len, DMA_FROM_DEVICE); + if (dma_mapping_error(jrdev, dst_dma)) { + dev_err(jrdev, "unable to map destination memory\n"); + return -ENOMEM; + } + + init_completion(done); + err = caam_jr_enqueue(jrdev, + caam_init_desc(desc, dst_dma), + caam_rng_done, &jctx); + if (err == -EINPROGRESS) { + wait_for_completion(done); + err = 0; + } + + dma_unmap_single(jrdev, dst_dma, len, DMA_FROM_DEVICE); + + return err ?: (ret ?: len); +} + +static void caam_rng_fill_async(struct caam_rng_ctx *ctx) +{ + struct scatterlist sg[1]; + struct completion done; + int len, nents; + + sg_init_table(sg, ARRAY_SIZE(sg)); + nents = kfifo_dma_in_prepare(&ctx->fifo, sg, ARRAY_SIZE(sg), + CAAM_RNG_MAX_FIFO_STORE_SIZE); + if (!nents) + return; + + len = caam_rng_read_one(ctx->jrdev, sg_virt(&sg[0]), + sg[0].length, + ctx->desc_async, + &done); + if (len < 0) + return; + + kfifo_dma_in_finish(&ctx->fifo, len); +} + +static void caam_rng_worker(struct work_struct *work) +{ + struct caam_rng_ctx *ctx = container_of(work, struct caam_rng_ctx, + worker); + caam_rng_fill_async(ctx); +} + +static int caam_read(struct hwrng *rng, void *dst, size_t max, bool wait) +{ + struct caam_rng_ctx *ctx = to_caam_rng_ctx(rng); + int out; + + if (wait) { + struct completion done; + + return caam_rng_read_one(ctx->jrdev, dst, max, + ctx->desc_sync, &done); + } + + out = kfifo_out(&ctx->fifo, dst, max); + if (kfifo_is_empty(&ctx->fifo)) + schedule_work(&ctx->worker); + + return out; +} + +static void caam_cleanup(struct hwrng *rng) +{ + struct caam_rng_ctx *ctx = to_caam_rng_ctx(rng); + + flush_work(&ctx->worker); + caam_jr_free(ctx->jrdev); + kfifo_free(&ctx->fifo); +} + +static int caam_init(struct hwrng *rng) +{ + struct caam_rng_ctx *ctx = to_caam_rng_ctx(rng); + int err; + + ctx->desc_sync = devm_kzalloc(ctx->ctrldev, CAAM_RNG_DESC_LEN, + GFP_DMA | GFP_KERNEL); + if (!ctx->desc_sync) + return -ENOMEM; + + ctx->desc_async = devm_kzalloc(ctx->ctrldev, CAAM_RNG_DESC_LEN, + GFP_DMA | GFP_KERNEL); + if (!ctx->desc_async) + return -ENOMEM; + + if (kfifo_alloc(&ctx->fifo, CAAM_RNG_MAX_FIFO_STORE_SIZE, + GFP_DMA | GFP_KERNEL)) + return -ENOMEM; + + INIT_WORK(&ctx->worker, caam_rng_worker); + + ctx->jrdev = caam_jr_alloc(); + err = PTR_ERR_OR_ZERO(ctx->jrdev); + if (err) { + kfifo_free(&ctx->fifo); + pr_err("Job Ring Device allocation for transform failed\n"); + return err; + } + + /* + * Fill async buffer to have early randomness data for + * hw_random + */ + caam_rng_fill_async(ctx); + + return 0; +} + +int caam_rng_init(struct device *ctrldev); + +void caam_rng_exit(struct device *ctrldev) +{ + devres_release_group(ctrldev, caam_rng_init); +} + +int caam_rng_init(struct device *ctrldev) +{ + struct caam_rng_ctx *ctx; + u32 rng_inst; + struct caam_drv_private *priv = dev_get_drvdata(ctrldev); + int ret; + + /* Check for an instantiated RNG before registration */ + if (priv->era < 10) + rng_inst = (rd_reg32(&priv->ctrl->perfmon.cha_num_ls) & + CHA_ID_LS_RNG_MASK) >> CHA_ID_LS_RNG_SHIFT; + else + rng_inst = rd_reg32(&priv->ctrl->vreg.rng) & CHA_VER_NUM_MASK; + + if (!rng_inst) + return 0; + + if (!devres_open_group(ctrldev, caam_rng_init, GFP_KERNEL)) + return -ENOMEM; + + ctx = devm_kzalloc(ctrldev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->ctrldev = ctrldev; + + ctx->rng.name = "rng-caam"; + ctx->rng.init = caam_init; + ctx->rng.cleanup = caam_cleanup; + ctx->rng.read = caam_read; + ctx->rng.priv = (unsigned long)ctx; + ctx->rng.quality = 1024; + + dev_info(ctrldev, "registering rng-caam\n"); + + ret = devm_hwrng_register(ctrldev, &ctx->rng); + if (ret) { + caam_rng_exit(ctrldev); + return ret; + } + + devres_close_group(ctrldev, caam_rng_init); + return 0; +} |