-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
crypto: qcom-rng - Add Qcom prng driver
This ports the Qcom prng from older hw_random driver. No change of functionality and move from hw_random to crypto APIs is done. Reviewed-by: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Vinod Koul <vkoul@kernel.org> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
- Loading branch information
Vinod Koul
authored and
Herbert Xu
committed
Jul 27, 2018
1 parent
d978b03
commit ceec5f5
Showing
3 changed files
with
220 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
// Copyright (c) 2017-18 Linaro Limited | ||
// | ||
// Based on msm-rng.c and downstream driver | ||
|
||
#include <crypto/internal/rng.h> | ||
#include <linux/clk.h> | ||
#include <linux/crypto.h> | ||
#include <linux/module.h> | ||
#include <linux/of.h> | ||
#include <linux/platform_device.h> | ||
|
||
/* Device specific register offsets */ | ||
#define PRNG_DATA_OUT 0x0000 | ||
#define PRNG_STATUS 0x0004 | ||
#define PRNG_LFSR_CFG 0x0100 | ||
#define PRNG_CONFIG 0x0104 | ||
|
||
/* Device specific register masks and config values */ | ||
#define PRNG_LFSR_CFG_MASK 0x0000ffff | ||
#define PRNG_LFSR_CFG_CLOCKS 0x0000dddd | ||
#define PRNG_CONFIG_HW_ENABLE BIT(1) | ||
#define PRNG_STATUS_DATA_AVAIL BIT(0) | ||
|
||
#define WORD_SZ 4 | ||
|
||
struct qcom_rng { | ||
struct mutex lock; | ||
void __iomem *base; | ||
struct clk *clk; | ||
}; | ||
|
||
struct qcom_rng_ctx { | ||
struct qcom_rng *rng; | ||
}; | ||
|
||
static struct qcom_rng *qcom_rng_dev; | ||
|
||
static int qcom_rng_read(struct qcom_rng *rng, u8 *data, unsigned int max) | ||
{ | ||
unsigned int currsize = 0; | ||
u32 val; | ||
|
||
/* read random data from hardware */ | ||
do { | ||
val = readl_relaxed(rng->base + PRNG_STATUS); | ||
if (!(val & PRNG_STATUS_DATA_AVAIL)) | ||
break; | ||
|
||
val = readl_relaxed(rng->base + PRNG_DATA_OUT); | ||
if (!val) | ||
break; | ||
|
||
if ((max - currsize) >= WORD_SZ) { | ||
memcpy(data, &val, WORD_SZ); | ||
data += WORD_SZ; | ||
currsize += WORD_SZ; | ||
} else { | ||
/* copy only remaining bytes */ | ||
memcpy(data, &val, max - currsize); | ||
break; | ||
} | ||
} while (currsize < max); | ||
|
||
return currsize; | ||
} | ||
|
||
static int qcom_rng_generate(struct crypto_rng *tfm, | ||
const u8 *src, unsigned int slen, | ||
u8 *dstn, unsigned int dlen) | ||
{ | ||
struct qcom_rng_ctx *ctx = crypto_rng_ctx(tfm); | ||
struct qcom_rng *rng = ctx->rng; | ||
int ret; | ||
|
||
ret = clk_prepare_enable(rng->clk); | ||
if (ret) | ||
return ret; | ||
|
||
mutex_lock(&rng->lock); | ||
|
||
ret = qcom_rng_read(rng, dstn, dlen); | ||
|
||
mutex_unlock(&rng->lock); | ||
clk_disable_unprepare(rng->clk); | ||
|
||
return 0; | ||
} | ||
|
||
static int qcom_rng_seed(struct crypto_rng *tfm, const u8 *seed, | ||
unsigned int slen) | ||
{ | ||
return 0; | ||
} | ||
|
||
static int qcom_rng_enable(struct qcom_rng *rng) | ||
{ | ||
u32 val; | ||
int ret; | ||
|
||
ret = clk_prepare_enable(rng->clk); | ||
if (ret) | ||
return ret; | ||
|
||
/* Enable PRNG only if it is not already enabled */ | ||
val = readl_relaxed(rng->base + PRNG_CONFIG); | ||
if (val & PRNG_CONFIG_HW_ENABLE) | ||
goto already_enabled; | ||
|
||
val = readl_relaxed(rng->base + PRNG_LFSR_CFG); | ||
val &= ~PRNG_LFSR_CFG_MASK; | ||
val |= PRNG_LFSR_CFG_CLOCKS; | ||
writel(val, rng->base + PRNG_LFSR_CFG); | ||
|
||
val = readl_relaxed(rng->base + PRNG_CONFIG); | ||
val |= PRNG_CONFIG_HW_ENABLE; | ||
writel(val, rng->base + PRNG_CONFIG); | ||
|
||
already_enabled: | ||
clk_disable_unprepare(rng->clk); | ||
|
||
return 0; | ||
} | ||
|
||
static int qcom_rng_init(struct crypto_tfm *tfm) | ||
{ | ||
struct qcom_rng_ctx *ctx = crypto_tfm_ctx(tfm); | ||
|
||
ctx->rng = qcom_rng_dev; | ||
|
||
return qcom_rng_enable(ctx->rng); | ||
} | ||
|
||
static struct rng_alg qcom_rng_alg = { | ||
.generate = qcom_rng_generate, | ||
.seed = qcom_rng_seed, | ||
.seedsize = 0, | ||
.base = { | ||
.cra_name = "stdrng", | ||
.cra_driver_name = "qcom-rng", | ||
.cra_flags = CRYPTO_ALG_TYPE_RNG, | ||
.cra_priority = 300, | ||
.cra_ctxsize = sizeof(struct qcom_rng_ctx), | ||
.cra_module = THIS_MODULE, | ||
.cra_init = qcom_rng_init, | ||
} | ||
}; | ||
|
||
static int qcom_rng_probe(struct platform_device *pdev) | ||
{ | ||
struct resource *res; | ||
struct qcom_rng *rng; | ||
int ret; | ||
|
||
rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); | ||
if (!rng) | ||
return -ENOMEM; | ||
|
||
platform_set_drvdata(pdev, rng); | ||
mutex_init(&rng->lock); | ||
|
||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
rng->base = devm_ioremap_resource(&pdev->dev, res); | ||
if (IS_ERR(rng->base)) | ||
return PTR_ERR(rng->base); | ||
|
||
rng->clk = devm_clk_get(&pdev->dev, "core"); | ||
if (IS_ERR(rng->clk)) | ||
return PTR_ERR(rng->clk); | ||
|
||
qcom_rng_dev = rng; | ||
ret = crypto_register_rng(&qcom_rng_alg); | ||
if (ret) { | ||
dev_err(&pdev->dev, "Register crypto rng failed: %d\n", ret); | ||
qcom_rng_dev = NULL; | ||
} | ||
|
||
return ret; | ||
} | ||
|
||
static int qcom_rng_remove(struct platform_device *pdev) | ||
{ | ||
crypto_unregister_rng(&qcom_rng_alg); | ||
|
||
qcom_rng_dev = NULL; | ||
|
||
return 0; | ||
} | ||
|
||
static const struct of_device_id qcom_rng_of_match[] = { | ||
{ .compatible = "qcom,prng" }, | ||
{} | ||
}; | ||
MODULE_DEVICE_TABLE(of, qcom_rng_of_match); | ||
|
||
static struct platform_driver qcom_rng_driver = { | ||
.probe = qcom_rng_probe, | ||
.remove = qcom_rng_remove, | ||
.driver = { | ||
.name = KBUILD_MODNAME, | ||
.of_match_table = of_match_ptr(qcom_rng_of_match), | ||
} | ||
}; | ||
module_platform_driver(qcom_rng_driver); | ||
|
||
MODULE_ALIAS("platform:" KBUILD_MODNAME); | ||
MODULE_DESCRIPTION("Qualcomm random number generator driver"); | ||
MODULE_LICENSE("GPL v2"); |