From: Sunil Goutham <sgoutham@xxxxxxxxxxx> CN10K series of silicons support true random number generators. This patch adds support for the same. Also supports entropy health status checking. Signed-off-by: Sunil Goutham <sgoutham@xxxxxxxxxxx> Signed-off-by: Bharat Bhushan <bbhushan2@xxxxxxxxxxx> Signed-off-by: Joseph Longever <jlongever@xxxxxxxxxxx> --- drivers/char/hw_random/Kconfig | 11 ++ drivers/char/hw_random/Makefile | 1 + drivers/char/hw_random/cn10k-rng.c | 205 +++++++++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 drivers/char/hw_random/cn10k-rng.c diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index 1da6477..001b819 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -538,6 +538,17 @@ config HW_RANDOM_ARM_SMCCC_TRNG To compile this driver as a module, choose M here: the module will be called arm_smccc_trng. +config HW_RANDOM_CN10K + tristate "Marvell CN10K Random Number Generator support" + depends on HW_RANDOM && PCI && ARM64 + default HW_RANDOM + help + This driver provides support for the True Random Number + generator available in Marvell CN10K SoCs. + + To compile this driver as a module, choose M here. + The module will be called cn10k_rng. If unsure, say Y. + endif # HW_RANDOM config UML_RANDOM diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index a5a1c76..a2f1ce0 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -46,3 +46,4 @@ obj-$(CONFIG_HW_RANDOM_NPCM) += npcm-rng.o obj-$(CONFIG_HW_RANDOM_CCTRNG) += cctrng.o obj-$(CONFIG_HW_RANDOM_XIPHERA) += xiphera-trng.o obj-$(CONFIG_HW_RANDOM_ARM_SMCCC_TRNG) += arm_smccc_trng.o +obj-$(CONFIG_HW_RANDOM_CN10K) += cn10k-rng.o diff --git a/drivers/char/hw_random/cn10k-rng.c b/drivers/char/hw_random/cn10k-rng.c new file mode 100644 index 0000000..e348732 --- /dev/null +++ b/drivers/char/hw_random/cn10k-rng.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Marvell CN10K RVU Hardware Random Number Generator. + * + * Copyright (C) 2021 Marvell. + * + */ + +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/delay.h> + +#include <linux/arm-smccc.h> + +/* CSRs */ +#define RNM_CTL_STATUS 0x000 +#define RNM_ENTROPY_STATUS 0x008 +#define RNM_CONST 0x030 +#define RNM_EBG_ENT 0x048 +#define RNM_PF_EBG_HEALTH 0x050 +#define RNM_PF_RANDOM 0x400 +#define RNM_TRNG_RESULT 0x408 + +struct cn10k_rng { + void __iomem *reg_base; + struct hwrng ops; + struct pci_dev *pdev; +}; + +#define PLAT_OCTEONTX_RESET_RNG_EBG_HEALTH_STATE 0xc2000b0f + +static int reset_rng_health_state(struct cn10k_rng *rng) +{ + struct arm_smccc_res res; + int ret = 0; + + /* Send SMC service call to reset EBG health state */ + arm_smccc_smc(PLAT_OCTEONTX_RESET_RNG_EBG_HEALTH_STATE, 0, 0, 0, 0, 0, 0, 0, &res); + if (res.a0 == 0UL) { + dev_info(&rng->pdev->dev, "HWRNG: reset completed\n"); + } else { + dev_err(&rng->pdev->dev, "HWRNG: error during reset\n"); + ret = -EIO; + } + + return ret; +} + +static int check_rng_health(struct cn10k_rng *rng) +{ + u64 status; + + /* Skip checking health */ + if (!rng->reg_base) + return 0; + + status = readq(rng->reg_base + RNM_PF_EBG_HEALTH); + if (status & BIT_ULL(20)) { + dev_err(&rng->pdev->dev, + "HWRNG: Health test failed (status=%llx)\n", status); + return reset_rng_health_state(rng); + } + return 0; +} + +static int cn10k_check_entropy(struct cn10k_rng *rng) +{ + int retries = 5; + u64 ent_status; + + while (retries) { + ent_status = readq(rng->reg_base + RNM_ENTROPY_STATUS); + if (ent_status & 0x7FULL) + break; + udelay(20); + retries--; + } + return ent_status & 0x7FULL; +} + +static int cn10k_read_trng(struct cn10k_rng *rng, u64 *value) +{ + u64 addr, result = 0; + int retry_count = 5; + + addr = (u64)rng->reg_base + RNM_PF_RANDOM; + /* TRNG and status need to be read at a time */ + while (!result && retry_count) { + __asm__ volatile("ldp %0,%1,[%2]" :\ + "=r" (*value), "=r" (result) : "r" (addr) : ); + + retry_count--; + } + + return retry_count ? 0 : -EIO; +} + +static int cn10k_rng_read(struct hwrng *hwrng, void *data, + size_t max, bool wait) +{ + struct cn10k_rng *rng = (struct cn10k_rng *)hwrng->priv; + unsigned int size; + int err = 0; + u64 value; + + err = check_rng_health(rng); + if (err) + return err; + + /* HW can run out of entropy if large amount random data is read in + * quick succession. So check if it's available to be read. + */ + size = cn10k_check_entropy(rng); + if (size > max) + size = max; + else + max = size; + + while (size >= 8) { + err = cn10k_read_trng(rng, &value); + if (err) + goto exit; + + *((u64 *)data) = (u64)value; + size -= 8; + data += 8; + } + + while (size > 0) { + err = cn10k_read_trng(rng, &value); + if (err) + goto exit; + + *((u8 *)data) = (u8)value; + size--; + data++; + } + +exit: + return max - size; +} + +static int cn10k_rng_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct cn10k_rng *rng; + int err; + + rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); + if (!rng) + return -ENOMEM; + + rng->pdev = pdev; + pci_set_drvdata(pdev, rng); + + rng->reg_base = pcim_iomap(pdev, 0, 0); + if (!rng->reg_base) { + dev_err(&pdev->dev, "Error while mapping CSRs, exiting\n"); + return -ENOMEM; + } + + rng->ops.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "cn10k-rng-%s", dev_name(&pdev->dev)); + if (!rng->ops.name) + return -ENOMEM; + + rng->ops.read = cn10k_rng_read; + rng->ops.quality = 1000; + rng->ops.priv = (unsigned long)rng; + + err = devm_hwrng_register(&pdev->dev, &rng->ops); + if (err) { + dev_err(&pdev->dev, "Could not register hwrng device.\n"); + return err; + } + + reset_rng_health_state(rng); + + return 0; +} + +static void cn10k_rng_remove(struct pci_dev *pdev) +{ + /* Nothing to do */ +} + +static const struct pci_device_id cn10k_rng_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xA098) }, /* RNG PF */ + {0,}, +}; + +MODULE_DEVICE_TABLE(pci, cn10k_rng_id_table); + +static struct pci_driver cn10k_rng_driver = { + .name = "cn10k_rng", + .id_table = cn10k_rng_id_table, + .probe = cn10k_rng_probe, + .remove = cn10k_rng_remove, +}; + +module_pci_driver(cn10k_rng_driver); +MODULE_AUTHOR("Sunil Goutham <sgoutham@xxxxxxxxxxx>"); +MODULE_DESCRIPTION("Marvell CN10K HW RNG Driver"); +MODULE_LICENSE("GPL v2"); -- 2.7.4