123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Hardware Random Number Generator support.
- * Cavium Thunder, Marvell OcteonTx/Tx2 processor families.
- *
- * Copyright (C) 2016 Cavium, Inc.
- */
- #include <linux/hw_random.h>
- #include <linux/io.h>
- #include <linux/module.h>
- #include <linux/pci.h>
- #include <linux/pci_ids.h>
- #include <asm/arch_timer.h>
- /* PCI device IDs */
- #define PCI_DEVID_CAVIUM_RNG_PF 0xA018
- #define PCI_DEVID_CAVIUM_RNG_VF 0xA033
- #define HEALTH_STATUS_REG 0x38
- /* RST device info */
- #define PCI_DEVICE_ID_RST_OTX2 0xA085
- #define RST_BOOT_REG 0x1600ULL
- #define CLOCK_BASE_RATE 50000000ULL
- #define MSEC_TO_NSEC(x) (x * 1000000)
- struct cavium_rng {
- struct hwrng ops;
- void __iomem *result;
- void __iomem *pf_regbase;
- struct pci_dev *pdev;
- u64 clock_rate;
- u64 prev_error;
- u64 prev_time;
- };
- static inline bool is_octeontx(struct pci_dev *pdev)
- {
- if (midr_is_cpu_model_range(read_cpuid_id(), MIDR_THUNDERX_83XX,
- MIDR_CPU_VAR_REV(0, 0),
- MIDR_CPU_VAR_REV(3, 0)) ||
- midr_is_cpu_model_range(read_cpuid_id(), MIDR_THUNDERX_81XX,
- MIDR_CPU_VAR_REV(0, 0),
- MIDR_CPU_VAR_REV(3, 0)) ||
- midr_is_cpu_model_range(read_cpuid_id(), MIDR_THUNDERX,
- MIDR_CPU_VAR_REV(0, 0),
- MIDR_CPU_VAR_REV(3, 0)))
- return true;
- return false;
- }
- static u64 rng_get_coprocessor_clkrate(void)
- {
- u64 ret = CLOCK_BASE_RATE * 16; /* Assume 800Mhz as default */
- struct pci_dev *pdev;
- void __iomem *base;
- pdev = pci_get_device(PCI_VENDOR_ID_CAVIUM,
- PCI_DEVICE_ID_RST_OTX2, NULL);
- if (!pdev)
- goto error;
- base = pci_ioremap_bar(pdev, 0);
- if (!base)
- goto error_put_pdev;
- /* RST: PNR_MUL * 50Mhz gives clockrate */
- ret = CLOCK_BASE_RATE * ((readq(base + RST_BOOT_REG) >> 33) & 0x3F);
- iounmap(base);
- error_put_pdev:
- pci_dev_put(pdev);
- error:
- return ret;
- }
- static int check_rng_health(struct cavium_rng *rng)
- {
- u64 cur_err, cur_time;
- u64 status, cycles;
- u64 time_elapsed;
- /* Skip checking health for OcteonTx */
- if (!rng->pf_regbase)
- return 0;
- status = readq(rng->pf_regbase + HEALTH_STATUS_REG);
- if (status & BIT_ULL(0)) {
- dev_err(&rng->pdev->dev, "HWRNG: Startup health test failed\n");
- return -EIO;
- }
- cycles = status >> 1;
- if (!cycles)
- return 0;
- cur_time = arch_timer_read_counter();
- /* RNM_HEALTH_STATUS[CYCLES_SINCE_HEALTH_FAILURE]
- * Number of coprocessor cycles times 2 since the last failure.
- * This field doesn't get cleared/updated until another failure.
- */
- cycles = cycles / 2;
- cur_err = (cycles * 1000000000) / rng->clock_rate; /* In nanosec */
- /* Ignore errors that happenned a long time ago, these
- * are most likely false positive errors.
- */
- if (cur_err > MSEC_TO_NSEC(10)) {
- rng->prev_error = 0;
- rng->prev_time = 0;
- return 0;
- }
- if (rng->prev_error) {
- /* Calculate time elapsed since last error
- * '1' tick of CNTVCT is 10ns, since it runs at 100Mhz.
- */
- time_elapsed = (cur_time - rng->prev_time) * 10;
- time_elapsed += rng->prev_error;
- /* Check if current error is a new one or the old one itself.
- * If error is a new one then consider there is a persistent
- * issue with entropy, declare hardware failure.
- */
- if (cur_err < time_elapsed) {
- dev_err(&rng->pdev->dev, "HWRNG failure detected\n");
- rng->prev_error = cur_err;
- rng->prev_time = cur_time;
- return -EIO;
- }
- }
- rng->prev_error = cur_err;
- rng->prev_time = cur_time;
- return 0;
- }
- /* Read data from the RNG unit */
- static int cavium_rng_read(struct hwrng *rng, void *dat, size_t max, bool wait)
- {
- struct cavium_rng *p = container_of(rng, struct cavium_rng, ops);
- unsigned int size = max;
- int err = 0;
- err = check_rng_health(p);
- if (err)
- return err;
- while (size >= 8) {
- *((u64 *)dat) = readq(p->result);
- size -= 8;
- dat += 8;
- }
- while (size > 0) {
- *((u8 *)dat) = readb(p->result);
- size--;
- dat++;
- }
- return max;
- }
- static int cavium_map_pf_regs(struct cavium_rng *rng)
- {
- struct pci_dev *pdev;
- /* Health status is not supported on 83xx, skip mapping PF CSRs */
- if (is_octeontx(rng->pdev)) {
- rng->pf_regbase = NULL;
- return 0;
- }
- pdev = pci_get_device(PCI_VENDOR_ID_CAVIUM,
- PCI_DEVID_CAVIUM_RNG_PF, NULL);
- if (!pdev) {
- pr_err("Cannot find RNG PF device\n");
- return -EIO;
- }
- rng->pf_regbase = ioremap(pci_resource_start(pdev, 0),
- pci_resource_len(pdev, 0));
- if (!rng->pf_regbase) {
- dev_err(&pdev->dev, "Failed to map PF CSR region\n");
- pci_dev_put(pdev);
- return -ENOMEM;
- }
- pci_dev_put(pdev);
- /* Get co-processor clock rate */
- rng->clock_rate = rng_get_coprocessor_clkrate();
- return 0;
- }
- /* Map Cavium RNG to an HWRNG object */
- static int cavium_rng_probe_vf(struct pci_dev *pdev,
- const struct pci_device_id *id)
- {
- struct cavium_rng *rng;
- int ret;
- rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL);
- if (!rng)
- return -ENOMEM;
- rng->pdev = pdev;
- /* Map the RNG result */
- rng->result = pcim_iomap(pdev, 0, 0);
- if (!rng->result) {
- dev_err(&pdev->dev, "Error iomap failed retrieving result.\n");
- return -ENOMEM;
- }
- rng->ops.name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
- "cavium-rng-%s", dev_name(&pdev->dev));
- if (!rng->ops.name)
- return -ENOMEM;
- rng->ops.read = cavium_rng_read;
- rng->ops.quality = 1000;
- pci_set_drvdata(pdev, rng);
- /* Health status is available only at PF, hence map PF registers. */
- ret = cavium_map_pf_regs(rng);
- if (ret)
- return ret;
- ret = devm_hwrng_register(&pdev->dev, &rng->ops);
- if (ret) {
- dev_err(&pdev->dev, "Error registering device as HWRNG.\n");
- return ret;
- }
- return 0;
- }
- /* Remove the VF */
- static void cavium_rng_remove_vf(struct pci_dev *pdev)
- {
- struct cavium_rng *rng;
- rng = pci_get_drvdata(pdev);
- iounmap(rng->pf_regbase);
- }
- static const struct pci_device_id cavium_rng_vf_id_table[] = {
- { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, PCI_DEVID_CAVIUM_RNG_VF) },
- { 0, }
- };
- MODULE_DEVICE_TABLE(pci, cavium_rng_vf_id_table);
- static struct pci_driver cavium_rng_vf_driver = {
- .name = "cavium_rng_vf",
- .id_table = cavium_rng_vf_id_table,
- .probe = cavium_rng_probe_vf,
- .remove = cavium_rng_remove_vf,
- };
- module_pci_driver(cavium_rng_vf_driver);
- MODULE_AUTHOR("Omer Khaliq <[email protected]>");
- MODULE_LICENSE("GPL v2");
|