mirror of
https://github.com/torvalds/linux.git
synced 2024-11-23 20:51:44 +00:00
hwrng: cn10k - Add random number generator support
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@marvell.com> Signed-off-by: Bharat Bhushan <bbhushan2@marvell.com> Signed-off-by: Joseph Longever <jlongever@marvell.com> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
This commit is contained in:
parent
223a41f549
commit
38e9791a02
@ -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
|
||||
|
@ -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
|
||||
|
181
drivers/char/hw_random/cn10k-rng.c
Normal file
181
drivers/char/hw_random/cn10k-rng.c
Normal file
@ -0,0 +1,181 @@
|
||||
// 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;
|
||||
|
||||
/* 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)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_rng_health(struct cn10k_rng *rng)
|
||||
{
|
||||
u64 status;
|
||||
int err;
|
||||
|
||||
/* Skip checking health */
|
||||
if (!rng->reg_base)
|
||||
return 0;
|
||||
|
||||
status = readq(rng->reg_base + RNM_PF_EBG_HEALTH);
|
||||
if (status & BIT_ULL(20)) {
|
||||
err = reset_rng_health_state(rng);
|
||||
if (err) {
|
||||
dev_err(&rng->pdev->dev, "HWRNG: Health test failed (status=%llx)\n",
|
||||
status);
|
||||
dev_err(&rng->pdev->dev, "HWRNG: error during reset\n");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cn10k_read_trng(struct cn10k_rng *rng, u64 *value)
|
||||
{
|
||||
u64 upper, lower;
|
||||
|
||||
*value = readq(rng->reg_base + RNM_PF_RANDOM);
|
||||
|
||||
/* HW can run out of entropy if large amount random data is read in
|
||||
* quick succession. Zeros may not be real random data from HW.
|
||||
*/
|
||||
if (!*value) {
|
||||
upper = readq(rng->reg_base + RNM_PF_RANDOM);
|
||||
lower = readq(rng->reg_base + RNM_PF_RANDOM);
|
||||
while (!(upper & 0x00000000FFFFFFFFULL))
|
||||
upper = readq(rng->reg_base + RNM_PF_RANDOM);
|
||||
while (!(lower & 0xFFFFFFFF00000000ULL))
|
||||
lower = readq(rng->reg_base + RNM_PF_RANDOM);
|
||||
|
||||
*value = (upper & 0xFFFFFFFF00000000) | (lower & 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
size = max;
|
||||
|
||||
while (size >= 8) {
|
||||
cn10k_read_trng(rng, &value);
|
||||
|
||||
*((u64 *)data) = (u64)value;
|
||||
size -= 8;
|
||||
data += 8;
|
||||
}
|
||||
|
||||
while (size > 0) {
|
||||
cn10k_read_trng(rng, &value);
|
||||
|
||||
*((u8 *)data) = (u8)value;
|
||||
size--;
|
||||
data++;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
reset_rng_health_state(rng);
|
||||
|
||||
err = devm_hwrng_register(&pdev->dev, &rng->ops);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "Could not register hwrng device.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
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@marvell.com>");
|
||||
MODULE_DESCRIPTION("Marvell CN10K HW RNG Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
Loading…
Reference in New Issue
Block a user