diff options
Diffstat (limited to 'drivers/crypto/qcom-rng.c')
| -rw-r--r-- | drivers/crypto/qcom-rng.c | 105 |
1 files changed, 76 insertions, 29 deletions
diff --git a/drivers/crypto/qcom-rng.c b/drivers/crypto/qcom-rng.c index e54249ccc009..0685ba122e8a 100644 --- a/drivers/crypto/qcom-rng.c +++ b/drivers/crypto/qcom-rng.c @@ -7,6 +7,10 @@ #include <linux/acpi.h> #include <linux/clk.h> #include <linux/crypto.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> @@ -25,33 +29,44 @@ #define WORD_SZ 4 +#define QCOM_TRNG_QUALITY 1024 + struct qcom_rng { struct mutex lock; void __iomem *base; struct clk *clk; - unsigned int skip_init; + struct hwrng hwrng; + struct qcom_rng_match_data *match_data; }; struct qcom_rng_ctx { struct qcom_rng *rng; }; +struct qcom_rng_match_data { + bool skip_init; + bool hwrng_support; +}; + 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; + int ret; /* read random data from hardware */ do { - val = readl_relaxed(rng->base + PRNG_STATUS); - if (!(val & PRNG_STATUS_DATA_AVAIL)) - break; + ret = readl_poll_timeout(rng->base + PRNG_STATUS, val, + val & PRNG_STATUS_DATA_AVAIL, + 200, 10000); + if (ret) + return ret; val = readl_relaxed(rng->base + PRNG_DATA_OUT); if (!val) - break; + return -EINVAL; if ((max - currsize) >= WORD_SZ) { memcpy(data, &val, WORD_SZ); @@ -60,7 +75,7 @@ static int qcom_rng_read(struct qcom_rng *rng, u8 *data, unsigned int max) } else { /* copy only remaining bytes */ memcpy(data, &val, max - currsize); - break; + currsize = max; } } while (currsize < max); @@ -86,7 +101,10 @@ static int qcom_rng_generate(struct crypto_rng *tfm, mutex_unlock(&rng->lock); clk_disable_unprepare(rng->clk); - return 0; + if (ret >= 0) + ret = 0; + + return ret; } static int qcom_rng_seed(struct crypto_rng *tfm, const u8 *seed, @@ -95,6 +113,13 @@ static int qcom_rng_seed(struct crypto_rng *tfm, const u8 *seed, return 0; } +static int qcom_hwrng_read(struct hwrng *hwrng, void *data, size_t max, bool wait) +{ + struct qcom_rng *qrng = container_of(hwrng, struct qcom_rng, hwrng); + + return qcom_rng_read(qrng, data, max); +} + static int qcom_rng_enable(struct qcom_rng *rng) { u32 val; @@ -130,7 +155,7 @@ static int qcom_rng_init(struct crypto_tfm *tfm) ctx->rng = qcom_rng_dev; - if (!ctx->rng->skip_init) + if (!ctx->rng->match_data->skip_init) return qcom_rng_enable(ctx->rng); return 0; @@ -153,7 +178,6 @@ static struct rng_alg qcom_rng_alg = { static int qcom_rng_probe(struct platform_device *pdev) { - struct resource *res; struct qcom_rng *rng; int ret; @@ -164,51 +188,74 @@ static int qcom_rng_probe(struct platform_device *pdev) 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); + rng->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(rng->base)) return PTR_ERR(rng->base); - /* ACPI systems have clk already on, so skip clk_get */ - if (!has_acpi_companion(&pdev->dev)) { - rng->clk = devm_clk_get(&pdev->dev, "core"); - if (IS_ERR(rng->clk)) - return PTR_ERR(rng->clk); - } - + rng->clk = devm_clk_get_optional(&pdev->dev, "core"); + if (IS_ERR(rng->clk)) + return PTR_ERR(rng->clk); - rng->skip_init = (unsigned long)device_get_match_data(&pdev->dev); + rng->match_data = (struct qcom_rng_match_data *)device_get_match_data(&pdev->dev); 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; + } + + if (rng->match_data->hwrng_support) { + rng->hwrng.name = "qcom_hwrng"; + rng->hwrng.read = qcom_hwrng_read; + rng->hwrng.quality = QCOM_TRNG_QUALITY; + ret = devm_hwrng_register(&pdev->dev, &rng->hwrng); + if (ret) { + dev_err(&pdev->dev, "Register hwrng failed: %d\n", ret); + qcom_rng_dev = NULL; + goto fail; + } } return ret; +fail: + crypto_unregister_rng(&qcom_rng_alg); + return ret; } -static int qcom_rng_remove(struct platform_device *pdev) +static void qcom_rng_remove(struct platform_device *pdev) { crypto_unregister_rng(&qcom_rng_alg); qcom_rng_dev = NULL; - - return 0; } -#if IS_ENABLED(CONFIG_ACPI) -static const struct acpi_device_id qcom_rng_acpi_match[] = { - { .id = "QCOM8160", .driver_data = 1 }, +static struct qcom_rng_match_data qcom_prng_match_data = { + .skip_init = false, + .hwrng_support = false, +}; + +static struct qcom_rng_match_data qcom_prng_ee_match_data = { + .skip_init = true, + .hwrng_support = false, +}; + +static struct qcom_rng_match_data qcom_trng_match_data = { + .skip_init = true, + .hwrng_support = true, +}; + +static const struct acpi_device_id __maybe_unused qcom_rng_acpi_match[] = { + { .id = "QCOM8160", .driver_data = (kernel_ulong_t)&qcom_prng_ee_match_data }, {} }; MODULE_DEVICE_TABLE(acpi, qcom_rng_acpi_match); -#endif -static const struct of_device_id qcom_rng_of_match[] = { - { .compatible = "qcom,prng", .data = (void *)0}, - { .compatible = "qcom,prng-ee", .data = (void *)1}, +static const struct of_device_id __maybe_unused qcom_rng_of_match[] = { + { .compatible = "qcom,prng", .data = &qcom_prng_match_data }, + { .compatible = "qcom,prng-ee", .data = &qcom_prng_ee_match_data }, + { .compatible = "qcom,trng", .data = &qcom_trng_match_data }, {} }; MODULE_DEVICE_TABLE(of, qcom_rng_of_match); |
