// 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; 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 <okhaliq@caviumnetworks.com>"); MODULE_LICENSE("GPL v2");