18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * RNG driver for AMD RNGs 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright 2005 (c) MontaVista Software, Inc. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * with the majority of the code coming from: 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG) 98c2ecf20Sopenharmony_ci * (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com> 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * derived from 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * Hardware driver for the AMD 768 Random Number Generator (RNG) 148c2ecf20Sopenharmony_ci * (c) Copyright 2001 Red Hat Inc 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * derived from 178c2ecf20Sopenharmony_ci * 188c2ecf20Sopenharmony_ci * Hardware driver for Intel i810 Random Number Generator (RNG) 198c2ecf20Sopenharmony_ci * Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com> 208c2ecf20Sopenharmony_ci * Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com> 218c2ecf20Sopenharmony_ci * 228c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public 238c2ecf20Sopenharmony_ci * License version 2. This program is licensed "as is" without any 248c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied. 258c2ecf20Sopenharmony_ci */ 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#include <linux/delay.h> 288c2ecf20Sopenharmony_ci#include <linux/hw_random.h> 298c2ecf20Sopenharmony_ci#include <linux/kernel.h> 308c2ecf20Sopenharmony_ci#include <linux/module.h> 318c2ecf20Sopenharmony_ci#include <linux/pci.h> 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define DRV_NAME "AMD768-HWRNG" 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci#define RNGDATA 0x00 368c2ecf20Sopenharmony_ci#define RNGDONE 0x04 378c2ecf20Sopenharmony_ci#define PMBASE_OFFSET 0xF0 388c2ecf20Sopenharmony_ci#define PMBASE_SIZE 8 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci/* 418c2ecf20Sopenharmony_ci * Data for PCI driver interface 428c2ecf20Sopenharmony_ci * 438c2ecf20Sopenharmony_ci * This data only exists for exporting the supported 448c2ecf20Sopenharmony_ci * PCI ids via MODULE_DEVICE_TABLE. We do not actually 458c2ecf20Sopenharmony_ci * register a pci_driver, because someone else might one day 468c2ecf20Sopenharmony_ci * want to register another driver on the same PCI id. 478c2ecf20Sopenharmony_ci */ 488c2ecf20Sopenharmony_cistatic const struct pci_device_id pci_tbl[] = { 498c2ecf20Sopenharmony_ci { PCI_VDEVICE(AMD, 0x7443), 0, }, 508c2ecf20Sopenharmony_ci { PCI_VDEVICE(AMD, 0x746b), 0, }, 518c2ecf20Sopenharmony_ci { 0, }, /* terminate list */ 528c2ecf20Sopenharmony_ci}; 538c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, pci_tbl); 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistruct amd768_priv { 568c2ecf20Sopenharmony_ci void __iomem *iobase; 578c2ecf20Sopenharmony_ci struct pci_dev *pcidev; 588c2ecf20Sopenharmony_ci u32 pmbase; 598c2ecf20Sopenharmony_ci}; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic int amd_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci u32 *data = buf; 648c2ecf20Sopenharmony_ci struct amd768_priv *priv = (struct amd768_priv *)rng->priv; 658c2ecf20Sopenharmony_ci size_t read = 0; 668c2ecf20Sopenharmony_ci /* We will wait at maximum one time per read */ 678c2ecf20Sopenharmony_ci int timeout = max / 4 + 1; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci /* 708c2ecf20Sopenharmony_ci * RNG data is available when RNGDONE is set to 1 718c2ecf20Sopenharmony_ci * New random numbers are generated approximately 128 microseconds 728c2ecf20Sopenharmony_ci * after RNGDATA is read 738c2ecf20Sopenharmony_ci */ 748c2ecf20Sopenharmony_ci while (read < max) { 758c2ecf20Sopenharmony_ci if (ioread32(priv->iobase + RNGDONE) == 0) { 768c2ecf20Sopenharmony_ci if (wait) { 778c2ecf20Sopenharmony_ci /* Delay given by datasheet */ 788c2ecf20Sopenharmony_ci usleep_range(128, 196); 798c2ecf20Sopenharmony_ci if (timeout-- == 0) 808c2ecf20Sopenharmony_ci return read; 818c2ecf20Sopenharmony_ci } else { 828c2ecf20Sopenharmony_ci return 0; 838c2ecf20Sopenharmony_ci } 848c2ecf20Sopenharmony_ci } else { 858c2ecf20Sopenharmony_ci *data = ioread32(priv->iobase + RNGDATA); 868c2ecf20Sopenharmony_ci data++; 878c2ecf20Sopenharmony_ci read += 4; 888c2ecf20Sopenharmony_ci } 898c2ecf20Sopenharmony_ci } 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci return read; 928c2ecf20Sopenharmony_ci} 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cistatic int amd_rng_init(struct hwrng *rng) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci struct amd768_priv *priv = (struct amd768_priv *)rng->priv; 978c2ecf20Sopenharmony_ci u8 rnen; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci pci_read_config_byte(priv->pcidev, 0x40, &rnen); 1008c2ecf20Sopenharmony_ci rnen |= BIT(7); /* RNG on */ 1018c2ecf20Sopenharmony_ci pci_write_config_byte(priv->pcidev, 0x40, rnen); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci pci_read_config_byte(priv->pcidev, 0x41, &rnen); 1048c2ecf20Sopenharmony_ci rnen |= BIT(7); /* PMIO enable */ 1058c2ecf20Sopenharmony_ci pci_write_config_byte(priv->pcidev, 0x41, rnen); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci return 0; 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic void amd_rng_cleanup(struct hwrng *rng) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci struct amd768_priv *priv = (struct amd768_priv *)rng->priv; 1138c2ecf20Sopenharmony_ci u8 rnen; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci pci_read_config_byte(priv->pcidev, 0x40, &rnen); 1168c2ecf20Sopenharmony_ci rnen &= ~BIT(7); /* RNG off */ 1178c2ecf20Sopenharmony_ci pci_write_config_byte(priv->pcidev, 0x40, rnen); 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic struct hwrng amd_rng = { 1218c2ecf20Sopenharmony_ci .name = "amd", 1228c2ecf20Sopenharmony_ci .init = amd_rng_init, 1238c2ecf20Sopenharmony_ci .cleanup = amd_rng_cleanup, 1248c2ecf20Sopenharmony_ci .read = amd_rng_read, 1258c2ecf20Sopenharmony_ci}; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_cistatic int __init mod_init(void) 1288c2ecf20Sopenharmony_ci{ 1298c2ecf20Sopenharmony_ci int err = -ENODEV; 1308c2ecf20Sopenharmony_ci struct pci_dev *pdev = NULL; 1318c2ecf20Sopenharmony_ci const struct pci_device_id *ent; 1328c2ecf20Sopenharmony_ci u32 pmbase; 1338c2ecf20Sopenharmony_ci struct amd768_priv *priv; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci for_each_pci_dev(pdev) { 1368c2ecf20Sopenharmony_ci ent = pci_match_id(pci_tbl, pdev); 1378c2ecf20Sopenharmony_ci if (ent) 1388c2ecf20Sopenharmony_ci goto found; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci /* Device not found. */ 1418c2ecf20Sopenharmony_ci return -ENODEV; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cifound: 1448c2ecf20Sopenharmony_ci err = pci_read_config_dword(pdev, 0x58, &pmbase); 1458c2ecf20Sopenharmony_ci if (err) 1468c2ecf20Sopenharmony_ci goto put_dev; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci pmbase &= 0x0000FF00; 1498c2ecf20Sopenharmony_ci if (pmbase == 0) { 1508c2ecf20Sopenharmony_ci err = -EIO; 1518c2ecf20Sopenharmony_ci goto put_dev; 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci priv = kzalloc(sizeof(*priv), GFP_KERNEL); 1558c2ecf20Sopenharmony_ci if (!priv) { 1568c2ecf20Sopenharmony_ci err = -ENOMEM; 1578c2ecf20Sopenharmony_ci goto put_dev; 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci if (!request_region(pmbase + PMBASE_OFFSET, PMBASE_SIZE, DRV_NAME)) { 1618c2ecf20Sopenharmony_ci dev_err(&pdev->dev, DRV_NAME " region 0x%x already in use!\n", 1628c2ecf20Sopenharmony_ci pmbase + 0xF0); 1638c2ecf20Sopenharmony_ci err = -EBUSY; 1648c2ecf20Sopenharmony_ci goto out; 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci priv->iobase = ioport_map(pmbase + PMBASE_OFFSET, PMBASE_SIZE); 1688c2ecf20Sopenharmony_ci if (!priv->iobase) { 1698c2ecf20Sopenharmony_ci pr_err(DRV_NAME "Cannot map ioport\n"); 1708c2ecf20Sopenharmony_ci err = -EINVAL; 1718c2ecf20Sopenharmony_ci goto err_iomap; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci amd_rng.priv = (unsigned long)priv; 1758c2ecf20Sopenharmony_ci priv->pmbase = pmbase; 1768c2ecf20Sopenharmony_ci priv->pcidev = pdev; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci pr_info(DRV_NAME " detected\n"); 1798c2ecf20Sopenharmony_ci err = hwrng_register(&amd_rng); 1808c2ecf20Sopenharmony_ci if (err) { 1818c2ecf20Sopenharmony_ci pr_err(DRV_NAME " registering failed (%d)\n", err); 1828c2ecf20Sopenharmony_ci goto err_hwrng; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci return 0; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cierr_hwrng: 1878c2ecf20Sopenharmony_ci ioport_unmap(priv->iobase); 1888c2ecf20Sopenharmony_cierr_iomap: 1898c2ecf20Sopenharmony_ci release_region(pmbase + PMBASE_OFFSET, PMBASE_SIZE); 1908c2ecf20Sopenharmony_ciout: 1918c2ecf20Sopenharmony_ci kfree(priv); 1928c2ecf20Sopenharmony_ciput_dev: 1938c2ecf20Sopenharmony_ci pci_dev_put(pdev); 1948c2ecf20Sopenharmony_ci return err; 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_cistatic void __exit mod_exit(void) 1988c2ecf20Sopenharmony_ci{ 1998c2ecf20Sopenharmony_ci struct amd768_priv *priv; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci priv = (struct amd768_priv *)amd_rng.priv; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci hwrng_unregister(&amd_rng); 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci ioport_unmap(priv->iobase); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci release_region(priv->pmbase + PMBASE_OFFSET, PMBASE_SIZE); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci pci_dev_put(priv->pcidev); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci kfree(priv); 2128c2ecf20Sopenharmony_ci} 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_cimodule_init(mod_init); 2158c2ecf20Sopenharmony_cimodule_exit(mod_exit); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ciMODULE_AUTHOR("The Linux Kernel team"); 2188c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("H/W RNG driver for AMD chipsets"); 2198c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 220