// 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 0 x000
#define RNM_ENTROPY_STATUS 0 x008
#define RNM_CONST 0 x030
#define RNM_EBG_ENT 0 x048
#define RNM_PF_EBG_HEALTH 0 x050
#define RNM_PF_RANDOM 0 x400
#define RNM_TRNG_RESULT 0 x408
/* Extended TRNG Read and Status Registers */
#define RNM_PF_TRNG_DAT 0 x1000
#define RNM_PF_TRNG_RES 0 x1008
struct cn10k_rng {
void __iomem *reg_base;
struct hwrng ops;
struct pci_dev *pdev;
/* Octeon CN10K-A A0/A1, CNF10K-A A0/A1 and CNF10K-B A0/B0
* does not support extended TRNG registers
*/
bool extended_trng_regs;
};
#define PLAT_OCTEONTX_RESET_RNG_EBG_HEALTH_STATE 0 xc2000b0f
#define PCI_SUBSYS_DEVID_CN10K_A_RNG 0 xB900
#define PCI_SUBSYS_DEVID_CNF10K_A_RNG 0 xBA00
#define PCI_SUBSYS_DEVID_CNF10K_B_RNG 0 xBC00
static bool cn10k_is_extended_trng_regs_supported(struct pci_dev *pdev)
{
/* CN10K-A A0/A1 */
if ((pdev->subsystem_device == PCI_SUBSYS_DEVID_CN10K_A_RNG) &&
(!pdev->revision || (pdev->revision & 0 xff) == 0 x50 ||
(pdev->revision & 0 xff) == 0 x51))
return false ;
/* CNF10K-A A0 */
if ((pdev->subsystem_device == PCI_SUBSYS_DEVID_CNF10K_A_RNG) &&
(!pdev->revision || (pdev->revision & 0 xff) == 0 x60 ||
(pdev->revision & 0 xff) == 0 x61))
return false ;
/* CNF10K-B A0/B0 */
if ((pdev->subsystem_device == PCI_SUBSYS_DEVID_CNF10K_B_RNG) &&
(!pdev->revision || (pdev->revision & 0 xff) == 0 x70 ||
(pdev->revision & 0 xff) == 0 x74))
return false ;
return true ;
}
static unsigned long 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);
return res.a0;
}
static int check_rng_health(struct cn10k_rng *rng)
{
u64 status;
unsigned long err;
/* Skip checking health */
if (!rng->reg_base)
return -ENODEV;
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 (error=%lx)\n" ,
err);
return -EIO;
}
}
return 0 ;
}
/* Returns true when valid data available otherwise return false */
static bool cn10k_read_trng(struct cn10k_rng *rng, u64 *value)
{
u16 retry_count = 0 ;
u64 upper, lower;
u64 status;
if (rng->extended_trng_regs) {
do {
*value = readq(rng->reg_base + RNM_PF_TRNG_DAT);
if (*value)
return true ;
status = readq(rng->reg_base + RNM_PF_TRNG_RES);
if (!status && (retry_count++ > 0 x1000))
return false ;
} while (!status);
}
*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 & 0 x00000000FFFFFFFFULL))
upper = readq(rng->reg_base + RNM_PF_RANDOM);
while (!(lower & 0 xFFFFFFFF00000000ULL))
lower = readq(rng->reg_base + RNM_PF_RANDOM);
*value = (upper & 0 xFFFFFFFF00000000) | (lower & 0 xFFFFFFFF);
}
return true ;
}
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;
u8 *pos = data;
int err = 0 ;
u64 value;
err = check_rng_health(rng);
if (err)
return err;
size = max;
while (size >= 8 ) {
if (!cn10k_read_trng(rng, &value))
goto out;
*((u64 *)pos) = value;
size -= 8 ;
pos += 8 ;
}
if (size > 0 ) {
if (!cn10k_read_trng(rng, &value))
goto out;
while (size > 0 ) {
*pos = (u8)value;
value >>= 8 ;
size--;
pos++;
}
}
out:
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)
return dev_err_probe(&pdev->dev, -ENOMEM, "Error while mapping CSRs, exiting\n" );
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.priv = (unsigned long )rng;
rng->extended_trng_regs = cn10k_is_extended_trng_regs_supported(pdev);
reset_rng_health_state(rng);
err = devm_hwrng_register(&pdev->dev, &rng->ops);
if (err)
return dev_err_probe(&pdev->dev, err, "Could not register hwrng device.\n" );
return 0 ;
}
static const struct pci_device_id cn10k_rng_id_table[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0 xA098) }, /* 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,
};
module_pci_driver(cn10k_rng_driver);
MODULE_AUTHOR("Sunil Goutham <sgoutham@marvell.com>" );
MODULE_DESCRIPTION("Marvell CN10K HW RNG Driver" );
MODULE_LICENSE("GPL v2" );
Messung V0.5 in Prozent C=95 H=84 G=89
[Seitenstruktur0.12Druckenetwas mehr zur Ethik2026-09-28]