summaryrefslogtreecommitdiff
path: root/drivers/char/hw_random/cn10k-rng.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/hw_random/cn10k-rng.c')
-rw-r--r--drivers/char/hw_random/cn10k-rng.c111
1 files changed, 79 insertions, 32 deletions
diff --git a/drivers/char/hw_random/cn10k-rng.c b/drivers/char/hw_random/cn10k-rng.c
index 35001c63648b..3b4e78182e14 100644
--- a/drivers/char/hw_random/cn10k-rng.c
+++ b/drivers/char/hw_random/cn10k-rng.c
@@ -23,34 +23,66 @@
#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
-static int reset_rng_health_state(struct cn10k_rng *rng)
+#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);
- if (res.a0 != 0UL)
- return -EIO;
-
- return 0;
+ return res.a0;
}
static int check_rng_health(struct cn10k_rng *rng)
{
u64 status;
- int err;
+ unsigned long err;
/* Skip checking health */
if (!rng->reg_base)
- return 0;
+ return -ENODEV;
status = readq(rng->reg_base + RNM_PF_EBG_HEALTH);
if (status & BIT_ULL(20)) {
@@ -58,15 +90,31 @@ static int check_rng_health(struct cn10k_rng *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\n");
+ dev_err(&rng->pdev->dev, "HWRNG: error during reset (error=%lx)\n",
+ err);
+ return -EIO;
}
}
return 0;
}
-static void cn10k_read_trng(struct cn10k_rng *rng, u64 *value)
+/* 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);
@@ -83,6 +131,7 @@ static void cn10k_read_trng(struct cn10k_rng *rng, u64 *value)
*value = (upper & 0xFFFFFFFF00000000) | (lower & 0xFFFFFFFF);
}
+ return true;
}
static int cn10k_rng_read(struct hwrng *hwrng, void *data,
@@ -90,6 +139,7 @@ static int cn10k_rng_read(struct hwrng *hwrng, void *data,
{
struct cn10k_rng *rng = (struct cn10k_rng *)hwrng->priv;
unsigned int size;
+ u8 *pos = data;
int err = 0;
u64 value;
@@ -100,21 +150,27 @@ static int cn10k_rng_read(struct hwrng *hwrng, void *data,
size = max;
while (size >= 8) {
- cn10k_read_trng(rng, &value);
+ if (!cn10k_read_trng(rng, &value))
+ goto out;
- *((u64 *)data) = (u64)value;
+ *((u64 *)pos) = value;
size -= 8;
- data += 8;
+ pos += 8;
}
- while (size > 0) {
- cn10k_read_trng(rng, &value);
+ if (size > 0) {
+ if (!cn10k_read_trng(rng, &value))
+ goto out;
- *((u8 *)data) = (u8)value;
- size--;
- data++;
+ while (size > 0) {
+ *pos = (u8)value;
+ value >>= 8;
+ size--;
+ pos++;
+ }
}
+out:
return max - size;
}
@@ -131,36 +187,28 @@ static int cn10k_rng_probe(struct pci_dev *pdev, const struct pci_device_id *id)
pci_set_drvdata(pdev, rng);
rng->reg_base = pcim_iomap(pdev, 0, 0);
- if (!rng->reg_base) {
- dev_err(&pdev->dev, "Error while mapping CSRs, exiting\n");
+ if (!rng->reg_base)
return -ENOMEM;
- }
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.quality = 1000;
+ 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) {
- dev_err(&pdev->dev, "Could not register hwrng device.\n");
- return err;
- }
+ if (err)
+ return dev_err_probe(&pdev->dev, err, "Could not register hwrng device.\n");
return 0;
}
-static void cn10k_rng_remove(struct pci_dev *pdev)
-{
- /* Nothing to do */
-}
-
static const struct pci_device_id cn10k_rng_id_table[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xA098) }, /* RNG PF */
{0,},
@@ -172,7 +220,6 @@ static struct pci_driver cn10k_rng_driver = {
.name = "cn10k_rng",
.id_table = cn10k_rng_id_table,
.probe = cn10k_rng_probe,
- .remove = cn10k_rng_remove,
};
module_pci_driver(cn10k_rng_driver);