// SPDX-License-Identifier: GPL-2.0 /* Marvell CN10K RVU Hardware Random Number Generator. * * Copyright (C) 2021 Marvell. * */ #include #include #include #include #include #include #include /* 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 /* Extended TRNG Read and Status Registers */ #define RNM_PF_TRNG_DAT 0x1000 #define RNM_PF_TRNG_RES 0x1008 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 0xc2000b0f #define PCI_SUBSYS_DEVID_CN10K_A_RNG 0xB900 #define PCI_SUBSYS_DEVID_CNF10K_A_RNG 0xBA00 #define PCI_SUBSYS_DEVID_CNF10K_B_RNG 0xBC00 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 & 0xff) == 0x50 || (pdev->revision & 0xff) == 0x51)) return false; /* CNF10K-A A0 */ if ((pdev->subsystem_device == PCI_SUBSYS_DEVID_CNF10K_A_RNG) && (!pdev->revision || (pdev->revision & 0xff) == 0x60 || (pdev->revision & 0xff) == 0x61)) return false; /* CNF10K-B A0/B0 */ if ((pdev->subsystem_device == PCI_SUBSYS_DEVID_CNF10K_B_RNG) && (!pdev->revision || (pdev->revision & 0xff) == 0x70 || (pdev->revision & 0xff) == 0x74)) 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++ > 0x1000)) 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 & 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); } 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, 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, }; module_pci_driver(cn10k_rng_driver); MODULE_AUTHOR("Sunil Goutham "); MODULE_DESCRIPTION("Marvell CN10K HW RNG Driver"); MODULE_LICENSE("GPL v2");