xref: /openbmc/linux/drivers/char/hw_random/amd-rng.c (revision 7e24a55b2122746c2eef192296fc84624354f895)
196d63c02SMichael Buesch /*
296d63c02SMichael Buesch  * RNG driver for AMD RNGs
396d63c02SMichael Buesch  *
496d63c02SMichael Buesch  * Copyright 2005 (c) MontaVista Software, Inc.
596d63c02SMichael Buesch  *
696d63c02SMichael Buesch  * with the majority of the code coming from:
796d63c02SMichael Buesch  *
896d63c02SMichael Buesch  * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG)
996d63c02SMichael Buesch  * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com>
1096d63c02SMichael Buesch  *
1196d63c02SMichael Buesch  * derived from
1296d63c02SMichael Buesch  *
1396d63c02SMichael Buesch  * Hardware driver for the AMD 768 Random Number Generator (RNG)
1477122d0bSAlan Cox  * (c) Copyright 2001 Red Hat Inc
1596d63c02SMichael Buesch  *
1696d63c02SMichael Buesch  * derived from
1796d63c02SMichael Buesch  *
1896d63c02SMichael Buesch  * Hardware driver for Intel i810 Random Number Generator (RNG)
1996d63c02SMichael Buesch  * Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com>
2096d63c02SMichael Buesch  * Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com>
2196d63c02SMichael Buesch  *
2296d63c02SMichael Buesch  * This file is licensed under  the terms of the GNU General Public
2396d63c02SMichael Buesch  * License version 2. This program is licensed "as is" without any
2496d63c02SMichael Buesch  * warranty of any kind, whether express or implied.
2596d63c02SMichael Buesch  */
2696d63c02SMichael Buesch 
27984e976fSPatrick McHardy #include <linux/delay.h>
28055ae890SCorentin LABBE #include <linux/hw_random.h>
291ce1cd82SRob Herring #include <linux/io.h>
30055ae890SCorentin LABBE #include <linux/kernel.h>
31055ae890SCorentin LABBE #include <linux/module.h>
32055ae890SCorentin LABBE #include <linux/pci.h>
3396d63c02SMichael Buesch 
34f8169bfbSCorentin LABBE #define DRV_NAME "AMD768-HWRNG"
3596d63c02SMichael Buesch 
363c343a37SCorentin LABBE #define RNGDATA		0x00
373c343a37SCorentin LABBE #define RNGDONE		0x04
383c343a37SCorentin LABBE #define PMBASE_OFFSET	0xF0
393c343a37SCorentin LABBE #define PMBASE_SIZE	8
403c343a37SCorentin LABBE 
4196d63c02SMichael Buesch /*
4296d63c02SMichael Buesch  * Data for PCI driver interface
4396d63c02SMichael Buesch  *
4496d63c02SMichael Buesch  * This data only exists for exporting the supported
4596d63c02SMichael Buesch  * PCI ids via MODULE_DEVICE_TABLE.  We do not actually
4696d63c02SMichael Buesch  * register a pci_driver, because someone else might one day
4796d63c02SMichael Buesch  * want to register another driver on the same PCI id.
4896d63c02SMichael Buesch  */
4996d63c02SMichael Buesch static const struct pci_device_id pci_tbl[] = {
50409a7363SJoe Perches 	{ PCI_VDEVICE(AMD, 0x7443), 0, },
51409a7363SJoe Perches 	{ PCI_VDEVICE(AMD, 0x746b), 0, },
5296d63c02SMichael Buesch 	{ 0, },	/* terminate list */
5396d63c02SMichael Buesch };
5496d63c02SMichael Buesch MODULE_DEVICE_TABLE(pci, pci_tbl);
5596d63c02SMichael Buesch 
567bad2cc0SCorentin LABBE struct amd768_priv {
573c343a37SCorentin LABBE 	void __iomem *iobase;
587bad2cc0SCorentin LABBE 	struct pci_dev *pcidev;
5969db7009SPrarit Bhargava 	u32 pmbase;
607bad2cc0SCorentin LABBE };
6196d63c02SMichael Buesch 
amd_rng_read(struct hwrng * rng,void * buf,size_t max,bool wait)6285962d22SCorentin LABBE static int amd_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait)
6396d63c02SMichael Buesch {
6485962d22SCorentin LABBE 	u32 *data = buf;
657bad2cc0SCorentin LABBE 	struct amd768_priv *priv = (struct amd768_priv *)rng->priv;
6685962d22SCorentin LABBE 	size_t read = 0;
6785962d22SCorentin LABBE 	/* We will wait at maximum one time per read */
6885962d22SCorentin LABBE 	int timeout = max / 4 + 1;
6996d63c02SMichael Buesch 
7085962d22SCorentin LABBE 	/*
7185962d22SCorentin LABBE 	 * RNG data is available when RNGDONE is set to 1
7285962d22SCorentin LABBE 	 * New random numbers are generated approximately 128 microseconds
7385962d22SCorentin LABBE 	 * after RNGDATA is read
7485962d22SCorentin LABBE 	 */
7585962d22SCorentin LABBE 	while (read < max) {
7685962d22SCorentin LABBE 		if (ioread32(priv->iobase + RNGDONE) == 0) {
7785962d22SCorentin LABBE 			if (wait) {
7885962d22SCorentin LABBE 				/* Delay given by datasheet */
7985962d22SCorentin LABBE 				usleep_range(128, 196);
8085962d22SCorentin LABBE 				if (timeout-- == 0)
8185962d22SCorentin LABBE 					return read;
8285962d22SCorentin LABBE 			} else {
8385962d22SCorentin LABBE 				return 0;
84984e976fSPatrick McHardy 			}
8585962d22SCorentin LABBE 		} else {
863c343a37SCorentin LABBE 			*data = ioread32(priv->iobase + RNGDATA);
8785962d22SCorentin LABBE 			data++;
8885962d22SCorentin LABBE 			read += 4;
8985962d22SCorentin LABBE 		}
9085962d22SCorentin LABBE 	}
9196d63c02SMichael Buesch 
9285962d22SCorentin LABBE 	return read;
9396d63c02SMichael Buesch }
9496d63c02SMichael Buesch 
amd_rng_init(struct hwrng * rng)9596d63c02SMichael Buesch static int amd_rng_init(struct hwrng *rng)
9696d63c02SMichael Buesch {
977bad2cc0SCorentin LABBE 	struct amd768_priv *priv = (struct amd768_priv *)rng->priv;
9896d63c02SMichael Buesch 	u8 rnen;
9996d63c02SMichael Buesch 
1007bad2cc0SCorentin LABBE 	pci_read_config_byte(priv->pcidev, 0x40, &rnen);
1011c335d44SCorentin LABBE 	rnen |= BIT(7);	/* RNG on */
1027bad2cc0SCorentin LABBE 	pci_write_config_byte(priv->pcidev, 0x40, rnen);
10396d63c02SMichael Buesch 
1047bad2cc0SCorentin LABBE 	pci_read_config_byte(priv->pcidev, 0x41, &rnen);
1051c335d44SCorentin LABBE 	rnen |= BIT(7);	/* PMIO enable */
1067bad2cc0SCorentin LABBE 	pci_write_config_byte(priv->pcidev, 0x41, rnen);
10796d63c02SMichael Buesch 
10896d63c02SMichael Buesch 	return 0;
10996d63c02SMichael Buesch }
11096d63c02SMichael Buesch 
amd_rng_cleanup(struct hwrng * rng)11196d63c02SMichael Buesch static void amd_rng_cleanup(struct hwrng *rng)
11296d63c02SMichael Buesch {
1137bad2cc0SCorentin LABBE 	struct amd768_priv *priv = (struct amd768_priv *)rng->priv;
11496d63c02SMichael Buesch 	u8 rnen;
11596d63c02SMichael Buesch 
1167bad2cc0SCorentin LABBE 	pci_read_config_byte(priv->pcidev, 0x40, &rnen);
1171c335d44SCorentin LABBE 	rnen &= ~BIT(7);	/* RNG off */
1187bad2cc0SCorentin LABBE 	pci_write_config_byte(priv->pcidev, 0x40, rnen);
11996d63c02SMichael Buesch }
12096d63c02SMichael Buesch 
12196d63c02SMichael Buesch static struct hwrng amd_rng = {
12296d63c02SMichael Buesch 	.name		= "amd",
12396d63c02SMichael Buesch 	.init		= amd_rng_init,
12496d63c02SMichael Buesch 	.cleanup	= amd_rng_cleanup,
12585962d22SCorentin LABBE 	.read		= amd_rng_read,
12696d63c02SMichael Buesch };
12796d63c02SMichael Buesch 
amd_rng_mod_init(void)128f0d9ff8cSRandy Dunlap static int __init amd_rng_mod_init(void)
12996d63c02SMichael Buesch {
130b7c3635eSColin Ian King 	int err;
13196d63c02SMichael Buesch 	struct pci_dev *pdev = NULL;
13296d63c02SMichael Buesch 	const struct pci_device_id *ent;
13396d63c02SMichael Buesch 	u32 pmbase;
1347bad2cc0SCorentin LABBE 	struct amd768_priv *priv;
13596d63c02SMichael Buesch 
13696d63c02SMichael Buesch 	for_each_pci_dev(pdev) {
13796d63c02SMichael Buesch 		ent = pci_match_id(pci_tbl, pdev);
13896d63c02SMichael Buesch 		if (ent)
13996d63c02SMichael Buesch 			goto found;
14096d63c02SMichael Buesch 	}
14196d63c02SMichael Buesch 	/* Device not found. */
1427bad2cc0SCorentin LABBE 	return -ENODEV;
14396d63c02SMichael Buesch 
14496d63c02SMichael Buesch found:
14596d63c02SMichael Buesch 	err = pci_read_config_dword(pdev, 0x58, &pmbase);
146*e91173e3SIlpo Järvinen 	if (err) {
147*e91173e3SIlpo Järvinen 		err = pcibios_err_to_errno(err);
148ecadb5b0SXiongfeng Wang 		goto put_dev;
149*e91173e3SIlpo Järvinen 	}
1507bad2cc0SCorentin LABBE 
15196d63c02SMichael Buesch 	pmbase &= 0x0000FF00;
152ecadb5b0SXiongfeng Wang 	if (pmbase == 0) {
153ecadb5b0SXiongfeng Wang 		err = -EIO;
154ecadb5b0SXiongfeng Wang 		goto put_dev;
155ecadb5b0SXiongfeng Wang 	}
1567bad2cc0SCorentin LABBE 
15769db7009SPrarit Bhargava 	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
158ecadb5b0SXiongfeng Wang 	if (!priv) {
159ecadb5b0SXiongfeng Wang 		err = -ENOMEM;
160ecadb5b0SXiongfeng Wang 		goto put_dev;
161ecadb5b0SXiongfeng Wang 	}
1627bad2cc0SCorentin LABBE 
16369db7009SPrarit Bhargava 	if (!request_region(pmbase + PMBASE_OFFSET, PMBASE_SIZE, DRV_NAME)) {
164f8169bfbSCorentin LABBE 		dev_err(&pdev->dev, DRV_NAME " region 0x%x already in use!\n",
165bd68ccb3SDmitry Eremin-Solenikov 			pmbase + 0xF0);
16669db7009SPrarit Bhargava 		err = -EBUSY;
16769db7009SPrarit Bhargava 		goto out;
168bd68ccb3SDmitry Eremin-Solenikov 	}
1693c343a37SCorentin LABBE 
17069db7009SPrarit Bhargava 	priv->iobase = ioport_map(pmbase + PMBASE_OFFSET, PMBASE_SIZE);
171f7eca278SWei Yongjun 	if (!priv->iobase) {
1723c343a37SCorentin LABBE 		pr_err(DRV_NAME "Cannot map ioport\n");
17369db7009SPrarit Bhargava 		err = -EINVAL;
17469db7009SPrarit Bhargava 		goto err_iomap;
1753c343a37SCorentin LABBE 	}
1763c343a37SCorentin LABBE 
1777bad2cc0SCorentin LABBE 	amd_rng.priv = (unsigned long)priv;
17869db7009SPrarit Bhargava 	priv->pmbase = pmbase;
1797bad2cc0SCorentin LABBE 	priv->pcidev = pdev;
18096d63c02SMichael Buesch 
181f8169bfbSCorentin LABBE 	pr_info(DRV_NAME " detected\n");
18269db7009SPrarit Bhargava 	err = hwrng_register(&amd_rng);
18369db7009SPrarit Bhargava 	if (err) {
18469db7009SPrarit Bhargava 		pr_err(DRV_NAME " registering failed (%d)\n", err);
18569db7009SPrarit Bhargava 		goto err_hwrng;
18669db7009SPrarit Bhargava 	}
18769db7009SPrarit Bhargava 	return 0;
18869db7009SPrarit Bhargava 
18969db7009SPrarit Bhargava err_hwrng:
19069db7009SPrarit Bhargava 	ioport_unmap(priv->iobase);
19169db7009SPrarit Bhargava err_iomap:
19269db7009SPrarit Bhargava 	release_region(pmbase + PMBASE_OFFSET, PMBASE_SIZE);
19369db7009SPrarit Bhargava out:
19469db7009SPrarit Bhargava 	kfree(priv);
195ecadb5b0SXiongfeng Wang put_dev:
196ecadb5b0SXiongfeng Wang 	pci_dev_put(pdev);
19769db7009SPrarit Bhargava 	return err;
19896d63c02SMichael Buesch }
19996d63c02SMichael Buesch 
amd_rng_mod_exit(void)200f0d9ff8cSRandy Dunlap static void __exit amd_rng_mod_exit(void)
20196d63c02SMichael Buesch {
20269db7009SPrarit Bhargava 	struct amd768_priv *priv;
20369db7009SPrarit Bhargava 
20469db7009SPrarit Bhargava 	priv = (struct amd768_priv *)amd_rng.priv;
20569db7009SPrarit Bhargava 
20669db7009SPrarit Bhargava 	hwrng_unregister(&amd_rng);
20769db7009SPrarit Bhargava 
20869db7009SPrarit Bhargava 	ioport_unmap(priv->iobase);
20969db7009SPrarit Bhargava 
21069db7009SPrarit Bhargava 	release_region(priv->pmbase + PMBASE_OFFSET, PMBASE_SIZE);
21169db7009SPrarit Bhargava 
212ecadb5b0SXiongfeng Wang 	pci_dev_put(priv->pcidev);
213ecadb5b0SXiongfeng Wang 
21469db7009SPrarit Bhargava 	kfree(priv);
21596d63c02SMichael Buesch }
21696d63c02SMichael Buesch 
217f0d9ff8cSRandy Dunlap module_init(amd_rng_mod_init);
218f0d9ff8cSRandy Dunlap module_exit(amd_rng_mod_exit);
21996d63c02SMichael Buesch 
22096d63c02SMichael Buesch MODULE_AUTHOR("The Linux Kernel team");
22196d63c02SMichael Buesch MODULE_DESCRIPTION("H/W RNG driver for AMD chipsets");
22296d63c02SMichael Buesch MODULE_LICENSE("GPL");
223