1 // SPDX-License-Identifier: GPL-2.0 2 /* Marvell CN10K RVU Hardware Random Number Generator. 3 * 4 * Copyright (C) 2021 Marvell. 5 * 6 */ 7 8 #include <linux/hw_random.h> 9 #include <linux/io.h> 10 #include <linux/module.h> 11 #include <linux/pci.h> 12 #include <linux/pci_ids.h> 13 #include <linux/delay.h> 14 15 #include <linux/arm-smccc.h> 16 17 /* CSRs */ 18 #define RNM_CTL_STATUS 0x000 19 #define RNM_ENTROPY_STATUS 0x008 20 #define RNM_CONST 0x030 21 #define RNM_EBG_ENT 0x048 22 #define RNM_PF_EBG_HEALTH 0x050 23 #define RNM_PF_RANDOM 0x400 24 #define RNM_TRNG_RESULT 0x408 25 26 struct cn10k_rng { 27 void __iomem *reg_base; 28 struct hwrng ops; 29 struct pci_dev *pdev; 30 }; 31 32 #define PLAT_OCTEONTX_RESET_RNG_EBG_HEALTH_STATE 0xc2000b0f 33 34 static unsigned long reset_rng_health_state(struct cn10k_rng *rng) 35 { 36 struct arm_smccc_res res; 37 38 /* Send SMC service call to reset EBG health state */ 39 arm_smccc_smc(PLAT_OCTEONTX_RESET_RNG_EBG_HEALTH_STATE, 0, 0, 0, 0, 0, 0, 0, &res); 40 return res.a0; 41 } 42 43 static int check_rng_health(struct cn10k_rng *rng) 44 { 45 u64 status; 46 unsigned long err; 47 48 /* Skip checking health */ 49 if (!rng->reg_base) 50 return -ENODEV; 51 52 status = readq(rng->reg_base + RNM_PF_EBG_HEALTH); 53 if (status & BIT_ULL(20)) { 54 err = reset_rng_health_state(rng); 55 if (err) { 56 dev_err(&rng->pdev->dev, "HWRNG: Health test failed (status=%llx)\n", 57 status); 58 dev_err(&rng->pdev->dev, "HWRNG: error during reset (error=%lx)\n", 59 err); 60 return -EIO; 61 } 62 } 63 return 0; 64 } 65 66 static void cn10k_read_trng(struct cn10k_rng *rng, u64 *value) 67 { 68 u64 upper, lower; 69 70 *value = readq(rng->reg_base + RNM_PF_RANDOM); 71 72 /* HW can run out of entropy if large amount random data is read in 73 * quick succession. Zeros may not be real random data from HW. 74 */ 75 if (!*value) { 76 upper = readq(rng->reg_base + RNM_PF_RANDOM); 77 lower = readq(rng->reg_base + RNM_PF_RANDOM); 78 while (!(upper & 0x00000000FFFFFFFFULL)) 79 upper = readq(rng->reg_base + RNM_PF_RANDOM); 80 while (!(lower & 0xFFFFFFFF00000000ULL)) 81 lower = readq(rng->reg_base + RNM_PF_RANDOM); 82 83 *value = (upper & 0xFFFFFFFF00000000) | (lower & 0xFFFFFFFF); 84 } 85 } 86 87 static int cn10k_rng_read(struct hwrng *hwrng, void *data, 88 size_t max, bool wait) 89 { 90 struct cn10k_rng *rng = (struct cn10k_rng *)hwrng->priv; 91 unsigned int size; 92 u8 *pos = data; 93 int err = 0; 94 u64 value; 95 96 err = check_rng_health(rng); 97 if (err) 98 return err; 99 100 size = max; 101 102 while (size >= 8) { 103 cn10k_read_trng(rng, &value); 104 105 *((u64 *)pos) = value; 106 size -= 8; 107 pos += 8; 108 } 109 110 if (size > 0) { 111 cn10k_read_trng(rng, &value); 112 113 while (size > 0) { 114 *pos = (u8)value; 115 value >>= 8; 116 size--; 117 pos++; 118 } 119 } 120 121 return max - size; 122 } 123 124 static int cn10k_rng_probe(struct pci_dev *pdev, const struct pci_device_id *id) 125 { 126 struct cn10k_rng *rng; 127 int err; 128 129 rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); 130 if (!rng) 131 return -ENOMEM; 132 133 rng->pdev = pdev; 134 pci_set_drvdata(pdev, rng); 135 136 rng->reg_base = pcim_iomap(pdev, 0, 0); 137 if (!rng->reg_base) { 138 dev_err(&pdev->dev, "Error while mapping CSRs, exiting\n"); 139 return -ENOMEM; 140 } 141 142 rng->ops.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, 143 "cn10k-rng-%s", dev_name(&pdev->dev)); 144 if (!rng->ops.name) 145 return -ENOMEM; 146 147 rng->ops.read = cn10k_rng_read; 148 rng->ops.priv = (unsigned long)rng; 149 150 reset_rng_health_state(rng); 151 152 err = devm_hwrng_register(&pdev->dev, &rng->ops); 153 if (err) { 154 dev_err(&pdev->dev, "Could not register hwrng device.\n"); 155 return err; 156 } 157 158 return 0; 159 } 160 161 static void cn10k_rng_remove(struct pci_dev *pdev) 162 { 163 /* Nothing to do */ 164 } 165 166 static const struct pci_device_id cn10k_rng_id_table[] = { 167 { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xA098) }, /* RNG PF */ 168 {0,}, 169 }; 170 171 MODULE_DEVICE_TABLE(pci, cn10k_rng_id_table); 172 173 static struct pci_driver cn10k_rng_driver = { 174 .name = "cn10k_rng", 175 .id_table = cn10k_rng_id_table, 176 .probe = cn10k_rng_probe, 177 .remove = cn10k_rng_remove, 178 }; 179 180 module_pci_driver(cn10k_rng_driver); 181 MODULE_AUTHOR("Sunil Goutham <sgoutham@marvell.com>"); 182 MODULE_DESCRIPTION("Marvell CN10K HW RNG Driver"); 183 MODULE_LICENSE("GPL v2"); 184