diff options
Diffstat (limited to 'drivers/char/hw_random/amd-rng.c')
-rw-r--r-- | drivers/char/hw_random/amd-rng.c | 140 |
1 files changed, 78 insertions, 62 deletions
diff --git a/drivers/char/hw_random/amd-rng.c b/drivers/char/hw_random/amd-rng.c index 48f6a83cdd61..4a99ac756f08 100644 --- a/drivers/char/hw_random/amd-rng.c +++ b/drivers/char/hw_random/amd-rng.c @@ -24,16 +24,18 @@ * warranty of any kind, whether express or implied. */ -#include <linux/module.h> +#include <linux/delay.h> +#include <linux/hw_random.h> #include <linux/kernel.h> +#include <linux/module.h> #include <linux/pci.h> -#include <linux/hw_random.h> -#include <linux/delay.h> -#include <asm/io.h> +#define DRV_NAME "AMD768-HWRNG" -#define PFX KBUILD_MODNAME ": " - +#define RNGDATA 0x00 +#define RNGDONE 0x04 +#define PMBASE_OFFSET 0xF0 +#define PMBASE_SIZE 8 /* * Data for PCI driver interface @@ -50,72 +52,84 @@ static const struct pci_device_id pci_tbl[] = { }; MODULE_DEVICE_TABLE(pci, pci_tbl); -static struct pci_dev *amd_pdev; - +struct amd768_priv { + void __iomem *iobase; + struct pci_dev *pcidev; +}; -static int amd_rng_data_present(struct hwrng *rng, int wait) +static int amd_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) { - u32 pmbase = (u32)rng->priv; - int data, i; - - for (i = 0; i < 20; i++) { - data = !!(inl(pmbase + 0xF4) & 1); - if (data || !wait) - break; - udelay(10); + u32 *data = buf; + struct amd768_priv *priv = (struct amd768_priv *)rng->priv; + size_t read = 0; + /* We will wait at maximum one time per read */ + int timeout = max / 4 + 1; + + /* + * RNG data is available when RNGDONE is set to 1 + * New random numbers are generated approximately 128 microseconds + * after RNGDATA is read + */ + while (read < max) { + if (ioread32(priv->iobase + RNGDONE) == 0) { + if (wait) { + /* Delay given by datasheet */ + usleep_range(128, 196); + if (timeout-- == 0) + return read; + } else { + return 0; + } + } else { + *data = ioread32(priv->iobase + RNGDATA); + data++; + read += 4; + } } - return data; -} -static int amd_rng_data_read(struct hwrng *rng, u32 *data) -{ - u32 pmbase = (u32)rng->priv; - - *data = inl(pmbase + 0xF0); - - return 4; + return read; } static int amd_rng_init(struct hwrng *rng) { + struct amd768_priv *priv = (struct amd768_priv *)rng->priv; u8 rnen; - pci_read_config_byte(amd_pdev, 0x40, &rnen); - rnen |= (1 << 7); /* RNG on */ - pci_write_config_byte(amd_pdev, 0x40, rnen); + pci_read_config_byte(priv->pcidev, 0x40, &rnen); + rnen |= BIT(7); /* RNG on */ + pci_write_config_byte(priv->pcidev, 0x40, rnen); - pci_read_config_byte(amd_pdev, 0x41, &rnen); - rnen |= (1 << 7); /* PMIO enable */ - pci_write_config_byte(amd_pdev, 0x41, rnen); + pci_read_config_byte(priv->pcidev, 0x41, &rnen); + rnen |= BIT(7); /* PMIO enable */ + pci_write_config_byte(priv->pcidev, 0x41, rnen); return 0; } static void amd_rng_cleanup(struct hwrng *rng) { + struct amd768_priv *priv = (struct amd768_priv *)rng->priv; u8 rnen; - pci_read_config_byte(amd_pdev, 0x40, &rnen); - rnen &= ~(1 << 7); /* RNG off */ - pci_write_config_byte(amd_pdev, 0x40, rnen); + pci_read_config_byte(priv->pcidev, 0x40, &rnen); + rnen &= ~BIT(7); /* RNG off */ + pci_write_config_byte(priv->pcidev, 0x40, rnen); } - static struct hwrng amd_rng = { .name = "amd", .init = amd_rng_init, .cleanup = amd_rng_cleanup, - .data_present = amd_rng_data_present, - .data_read = amd_rng_data_read, + .read = amd_rng_read, }; - static int __init mod_init(void) { int err = -ENODEV; struct pci_dev *pdev = NULL; const struct pci_device_id *ent; u32 pmbase; + struct amd768_priv *priv; for_each_pci_dev(pdev) { ent = pci_match_id(pci_tbl, pdev); @@ -123,42 +137,44 @@ static int __init mod_init(void) goto found; } /* Device not found. */ - goto out; + return -ENODEV; found: err = pci_read_config_dword(pdev, 0x58, &pmbase); if (err) - goto out; - err = -EIO; + return err; + pmbase &= 0x0000FF00; if (pmbase == 0) - goto out; - if (!request_region(pmbase + 0xF0, 8, "AMD HWRNG")) { - dev_err(&pdev->dev, "AMD HWRNG region 0x%x already in use!\n", + return -EIO; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (!devm_request_region(&pdev->dev, pmbase + PMBASE_OFFSET, + PMBASE_SIZE, DRV_NAME)) { + dev_err(&pdev->dev, DRV_NAME " region 0x%x already in use!\n", pmbase + 0xF0); - err = -EBUSY; - goto out; + return -EBUSY; } - amd_rng.priv = (unsigned long)pmbase; - amd_pdev = pdev; - - pr_info("AMD768 RNG detected\n"); - err = hwrng_register(&amd_rng); - if (err) { - pr_err(PFX "RNG registering failed (%d)\n", - err); - release_region(pmbase + 0xF0, 8); - goto out; + + priv->iobase = devm_ioport_map(&pdev->dev, pmbase + PMBASE_OFFSET, + PMBASE_SIZE); + if (!priv->iobase) { + pr_err(DRV_NAME "Cannot map ioport\n"); + return -ENOMEM; } -out: - return err; + + amd_rng.priv = (unsigned long)priv; + priv->pcidev = pdev; + + pr_info(DRV_NAME " detected\n"); + return devm_hwrng_register(&pdev->dev, &amd_rng); } static void __exit mod_exit(void) { - u32 pmbase = (unsigned long)amd_rng.priv; - release_region(pmbase + 0xF0, 8); - hwrng_unregister(&amd_rng); } module_init(mod_init); |